promise
Promise 是异步编程的一种解决方案,主要解决回调地狱的问题,因为有的需求需要先完成某一个步骤,再进行下一个步骤,或者下一个步骤需要上一个步骤的结果。
三种状态:
pending(进行中)、resolved | fulfilled(火狐)(已成功)和rejected(已失败)
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为resolved 和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise.prototype.then()
示例:
const p= new Promise((resolve, reject)=> {
// ... some code
if (/* 异步操作成功 */){
resolve('成功');
} else {
reject(error);
}
})
p.then((res)=> {
console.log("成功回调",res);
}, (err)=> {
console.log("失败回调 ",err);
});
//输出"成功回调",成功 或者失败回调 ",失败原因,上一步执行完的结果或者传参,在下一步可以获取
示例2:
let promise = new Promise((resolve, reject) =>{
console.log('Promise');
resolve();
});
promise.then(()=> {
console.log('resolved.');
});
console.log('Hi!');
//输出 Promise Hi! resolved
//Promise 新建后立即执行,所以首先输出的是Promise。
//然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
示例3:
const p1 = new Promise((resolve, reject)=> {
reject(new Error())
});
const p2 = new Promise((resolve, reject)=> {
resolve(p1);
})
> resolve可带普通参数给then回调,也可带对象。reject一般带Error对象的实例,表示抛出的错误
Promise.prototype.catch()
示例4
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
}).catch(error=> console.log(error));
//输出 2 1
//调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。
//这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
.catch接受运行中抛出错误,建议总是使用catch()方法,因为可以接收所有promise运行时的错误 虽然使用reject也可以接受这个错误,但其只针对一个回调函数,catch可以获取所有回调的错误
Promise.prototype.finally()
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
promise.then(result => {···})
.catch(error => {···})
.finally(() => {···});
Promise.all()
有一种需求,即前面三个请求,全部完成以后,才能去完成第四个请求,这时候all方法就派上用场了。 Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 示例5:
const databasePromise =new Promise((resolve,reject)=>{
//连接数据库
})
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
const p = Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));
- Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
- booksPromise、userPromise的状态都变成resolved,p的状态才会变成resolved,返回值组成数组传递给p的回调函数
- 只要有一个一个结果返回的是rejected,p的状态都会变成rejected,并返回第一个状态为rejected的返回值作为回调函数的参数
如果回调函数内部定义了.catch(e => e);方法,则就算报错,也会返回resolved。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
}).then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
}.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
//输出 // ["hello", Error: 报错了]
//这种情况下,就算p1 p2有执行错误,还是会返回resolved。其错误在内部消化了
Promise.allSettled()
有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。但是,现有的 Promise 方法很难实现这个要求。Promise.all()方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。
Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例, 与all的区别主要是:
const p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.any()
参数实例有一个变成fulfilled状态,包装实例就会变成resolved状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
-
上面代码将 jQuery 生成的deferred对象,转为一个新的 Promise 对象 -
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。 -
参数根本就不是对象
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
// 输出Hello
- Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的
const p = Promise.resolve();
p.then(function () {
// ...
});
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, (s)=> {
console.log(s)
});
// 出错了
Promise.reject('出错了')
.catch(e => {
console.log(e === '出错了')
})
// true
Promise.try()
promise.catch()捕获所有同步和异步的错误。
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
Generator 函数
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
function* func(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
f.next();
// one
// {value: "1", done: false}
f.next();
// two
// {value: "2", done: false}
f.next();
// three
// {value: "3", done: true}
f.next();
// {value: undefined, done: true}
第一次调用 next 方法时,从 Generator 函数的头部开始执行,先是打印了 one ,执行到 yield 就停下来,并将yield 后边表达式的值 ‘1’,作为返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false。
第二次调用 next 方法时,同上步 。
第三次调用 next 方法时,先是打印了 three ,然后执行了函数的返回操作,并将 return 后面的表达式的值,作为返回对象的 value 属性值,此时函数已经结束,多以 done 属性值为true 。
第四次调用 next 方法时, 此时函数已经执行完了,所以返回 value 属性值是 undefined ,done 属性值是 true 。如果执行第三步时,没有 return 语句的话,就直接返回 {value: undefined, done: true}。
next 方法
一般情况下,next 方法不传入参数的时候,yield 表达式的返回值是 undefined 。当 next 传入参数的时候,该参数会作为上一步yield的返回值。
function* sendParameter(){
var x = yield '2';
console.log("one:" + x);
var y = yield '3';
console.log("two:" + y);
console.log("total:" + (x + y));
}
next不传参
var sendp1 = sendParameter();
sendp1.next();
// {value: "2", done: false}
sendp1.next();
// one:undefined
// {value: "3", done: false}
sendp1.next();
// two:undefined
// total:NaN
// {value: undefined, done: true}
next传参
var sendp2 = sendParameter();
sendp2.next(10);
// {value: "2", done: false}
sendp2.next(20);
// one:20
// {value: "3", done: false}
sendp2.next(30);
// two:30
// total:50
// {value: undefined, done: true}
return 方法
return 方法返回给定值,并结束遍历 Generator 函数。 return 方法提供参数时,返回该参数;不提供参数时,返回 undefined
function* foo(){
yield 1;
yield 2;
yield 3;
}
var f = foo();
f.next();
// {value: 1, done: false}
f.return("foo");
// {value: "foo", done: true}
f.next();
// {value: undefined, done: true}
async/await和 Generator 函数区别
异步操作,同步执行,属于ES7中的内容 async 函数是什么?一句话,它就是 Generator 函数的语法糖。 Generator 是ES6中的内容,说白了就是Generator 的升级版,
有一个 Generator 函数,依次读取两个文件
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
上面代码的函数gen可以写成async函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点。 1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
asyncReadFile(); 上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
async/await
async 是 ES7 才有的与异步操作有关的关键字
const p = async ()=>{
}
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。 async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。 例子:
function testAwait(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("testAwait");
resolve();
}, 1000);
});
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync
await
await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。 await 后面通常是一个Promise 对象或者任何要等待的值,返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。 例子:
function testAwait (x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function helloAsync() {
var x = await testAwait ("hello world");
console.log(x);
}
helloAsync ();
// hello world
ES6 模块化
ES6 的模块化分为导出(export) @与导入(import)两个模块。
- ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;。
- 模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
- 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
- 每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
export 与 import
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass = class myClass {
static a = "yeah!";
}
export { myName, myAge, myfn, myClass }//导出
import { myName, myAge, myfn, myClass } from "./test.js"; //导入
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!
as的用法 export { myName as exportName } 将导出命别名
import 命令的特点
不允许import 的模块重新指向
import {a} from "./xxx.js"
a = {}; // error
单例模式:多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 。
export default 命令
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个
- export default 向外暴露的成员,可以使用任意变量来接收。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 中的 default 是对应的导出接口变量。
const p = new promise((resolve,reject)=>{
return 1111
})
export default p
|