Promise详解
Promise 基本用法
- promise有三个状态:
pending
[待定] 初始状态 |fulfilled
[实现] 成功 |rejected
[被否决] 失败
当promise状态发生改变时,就会触发then()里的响应函数处理后续步骤;
promise 状态一经改变,状态固化,不会再改变Promise
有两种状态改变的方式,既可以从pending
转变为fulfilled
,也可以从pending
转变为rejected
.一旦状态改变,就固化了,会一直保持这个状态,不会再发生变化。当状态发生变化时,promoise.then绑定的函数会被立即调用。new Promise()
是同步执行,一旦新建会「立即执行」,无法取消。
使用new
构建一个Promise
对象.Promise
接受一个函数参数,该函数的两个参数分别是resolve
和reject
.这两个函数就是回调函数.resolve
函数的作用: 在异步操作成功时调用,并将异步操作的结果作为参数传递出去;reject
函数: 在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去.
Promise实例生成后,可以用then
方法指定resolved
状态和rejected
状态的回调函数.
1 | // 构建Promise |
then
方法会返回一个Promise
对象,它有两个参数,分别为Promise从pending
变为fulfilled
和rejected
时的回调函数(第二个参数非必选). 这两个函数都接受Promise对象传出的值作为参数.then
就是定义resolve
和reject
函数的,其resolve
参数相当于:
1 | function resolveFun(data){ |
新建Promise中的’resolve(data)’,相当于执行resolveFun函数.
Promise新建后就会立即执行.then
方法中指定的回电函数,将当前脚本所有同步任务执行完成后才会执行.
1 | var promise = new Promise( function(resolve, reject){ |
由于resolve
指定的是异步操作成功后的回调函数,他需要等所有同步代码执行完成后才会执行,因此最后打印 3。
基本API
.then()
语法: Promise.prototype.then(onFulfilled, onRejected)
对promise添加onFulfilled
和onRejected
回调, 并返回的是一个新的Promise实例(不是.then前的那个Promise实例),且将返回值作为参数传入这个新的Promise对象的resolver
函数
因此,我们可以使用链式写法。由于前一个回调函数返回的还是一个Promise对象(即有异步操作),后一个回调函数会等待该Promise对象的状态发生变化,才会被调用
1 | /* ajax异步回调 */ |
.catch()
语法: Promise.prototype.catch(onRejected)
该方法是.then(null, onRejected)的另一种写法,用于指定发生错误时的回调函数.
1 | promise.then( function(data){}) |
1 | var promise = new Promise( function(resolve, reject){ |
reject
等同于抛出错误。
promise对象的错误,会一直向后传递,直到被捕获。 即错误总会被下一个catch
所捕获,then
方法指定的回调函数若抛出错误,也会被下一个catch
捕获. catch
中也能抛错,则需要后面的catch
捕获.
1 | test('test1.html').then( function(data1){ |
上文提到过,promise状态一旦改变就会凝固,不会再改变。因此promise一旦fulfilled
了,再抛错,也不会变为rejected
,就不会被catch
了。
1 | var promise = new Promise( function(resolve, reject){ |
如果没有使用catch
方法指定处理错误的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应(Chrome会抛错),这是Promise的另一个缺点
.all()
语法: Promise.all(iterable)
该方法用于将多个Promise实例,包装成一个新的Promise实例。
1 | var p = Promise.all([p1, p2, p3]); |
Promise.all
方法接受一个数组(或具有Iterator接口)作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve
转换为一个promise)。它的状态由这三个promise实例决定。
当p1, p2, p3状态都变为
fulfilled
,p的状态才会变为fulfilled
,并将三个promise返回的结果,按参数的顺序(而不是resolved
的顺序)存入数组,传给p的回调函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 3000, "first");
});
var p2 = new Promise(function (resolve, reject) {
resolve('second');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "third");
});
Promise.all([p1, p2, p3]).then(function(values) {
console.log(values);
});
//约 3s 后 ["first", "second", "third"]当p1, p2, p3其中之一状态变为
rejected
,p的状态也会变为rejected
,并把第一个被reject
的promise的返回值,传给p的回调函数,如例3.9。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "one");
});
var p2 = new Promise((resolve, reject) => {
setTimeout(reject, 2000, "two");
});
var p3 = new Promise((resolve, reject) => {
reject("three");
});
Promise.all([p1, p2, p3]).then(function (value) {
console.log('resolve', value);
}, function (error) {
console.log('reject', error); // => reject three
});
// 输出 reject three这多个 promise 是同时开始、并行执行的,而不是顺序执行。从下面例子可以看出。如果一个个执行,那至少需要 1+32+64+128
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (values) {
console.log(Date.now() - startDate + 'ms');
console.log(values);
});
// 输出:133ms //不一定,但大于128ms
// [1,32,64,128]
.race()
语法: Promise.race(iterable)
该方法同样是将多个Promise实例,包装成一个新的Promise实例
1 | p = Promise.race([p1,p2,p3]) |
Promise.race
方法同样接受一个数组(或具有Iterator接口)作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled
或rejected
),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。
1 | var p1 = new Promise(function(resolve, reject) { |
在第一个promise对象变为resolve后,并不会取消其他promise对象的执行:
1 | var fastPromise = new Promise(function (resolve) { |
.resolve()
语法:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
可以看成new Promise
的快捷方式
1 | Promise.resolve('Success'); |
这段代码会让这个Promise对象立即进入resolved
状态,并将结果success
传递给then指定的onFulfilled
回调函数。由于Promise.resolve()
也是返回Promise对象,因此可以用.then()
处理其返回值。
1 | Promise.resolve('success').then(function (value) { |
1 | //Resolving an array |
Promise.resolve()
的另一个作用就是将thenable
对象(即带有then
方法的对象)转换为promise
对象。
1 | var p1 = Promise.resolve({ |
再看下面两个例子,无论是在什么时候抛异常,只要promise状态变成resolved
或rejected
,状态不会再改变,这和新建promise是一样的。
1 | var p1 = { |
.reject()
语法: Promise.reject(reason)
这个方法和上述的Promise.resolve()类似,它也是new Promise()的快捷方式。
1 | Promise.reject(new Error('error')); |
这段代码会让这个Promise对象立即进入rejected
状态,并将错误对象传递给then
指定的onRejected
回调函数。
常见问题
reject 和 catch 的区别
- 使用new Promise(fn)或者它的快捷方式
Promise.resolve()
、Promise.reject()
,返回一个promise对象 - 在
fn
中指定异步的处理
处理结果正常,调用resolve
处理结果错误,调用reject
一般情况,还是建议使用第二种,因为能捕获之前的所有异常。第二种的.catch()
也可以使用.then()
表示,它们本质上是没有区别的,.catch === .then(null, onRejected)
如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误
1 | function taskA() { |
上面例子的输出结果及流程图,可以看出,A抛错时,会按照 taskA → onRejected → finalTask这个流程来处理。A抛错后,若没有对它进行处理,状态就会维持rejected
,taskB不会执行,直到catch
了错误。
1 | function taskA() { |
将本例与上面的例子对比,在taskA后多了对A的处理,因此,A抛错时,会按照A会按照 taskA → onRejectedA → taskB → finalTask这个流程来处理,此时taskB是正常执行的。
每次调用then都会返回一个新创建的promise对象,而then内部只是返回的数据
1 | //方法1:对同一个promise对象同时调用 then 方法 |
第一种方法中,then
的调用几乎是同时开始执行的,且传给每个then的value都是100,这种方法应当避免。方法二才是正确的链式调用。
因此容易出现下面的错误写法:
1 | function badAsyncCall(data) { |
正确的写法应该是:
1 | function goodAsyncCall(data) { |
在异步回调中抛错,不会被catch到
1 | // 异步函数中抛出的错误将像未捕获的错误一样 |
promise状态变为resove或reject,就凝固了,不会再改变
1 | console.log(1); |