前两天看一篇公众号文章从一道面试题谈谈对 EventLoop 的理解,里面讲到了JS的事件循环,开篇第一题和打怪进阶的第一题“黄金题”我还都会做,但是到了打怪进阶的第二题“砖石题”,我就懵了,怎么也想不通为什么。
然后开始查阅各种资料,看 Promise A+ 规范,也没搞懂为什么。这个问题在困扰了我两天之后,在昨晚和同事的交流中,终于得到了解答。归根结底还是我自己对规范理解的不够透彻。
所以下面先跟着我一起来重新理解一下Promise A+ 规范规范吧。
A promise must provide a
then
method to access its current or eventual value or reason.
A promise’sthen
method accepts two arguments:promise.then(onFulfilled, onRejected)
- 2.2.2 If
onFulfilled
is a function:
- 2.2.2.1 it must be called after
promise
is fulfilled, withpromise
’s value as its first argument.- 2.2.2.2 it must not be called before
promise
is fulfilled.- 2.2.2.3 it must not be called more than once.
翻译一下就是: promise必须提供一个then
方法来存取它当前或最终的值或者原因。promise的then
方法接收两个参数:1
promise.then(onFulfilled, onRejected)
如果 onFulfilled
是函数,此函数必须在promise 完成(fulfilled)后被调用,并把promise 的值作为它的第一个参数;此函数在promise
完成(fulfilled)之前绝对不能被调用;此函数绝对不能被调用超过一次。
- 2.2.6
then
may be called multiple times on the same promise.
- 2.2.6.1 If/when
promise
is fulfilled, all respectiveonFulfilled
callbacks must execute in the order of their originating calls tothen
.- 2.2.6.2 If/when
promise
is rejected, all respectiveonRejected
callbacks must execute in the order of their originating calls tothen
.
翻译一下就是: then
在同一个promise里可以被调用多次,并且当promise的状态变为fulfilled
或者rejected
时,onFulfilled
和onRejected
回调函数的调用顺序将会按照在then里定义的顺序进行调用。
也就是像下面的代码那样:1
2
3
4
5
6
7
8
9
10
11
12const promise1 = new Promise((resolve, reject) => {
console.log(1);
resolve();
});
promise1.then(() => {
console.log(2);
});
promise1.then(() => {
console.log(3);
})
- 2.2.7
then
must return a promise.
翻译一下就是: then
必须返回一个promise。也是因为这个规范,所以 promise 支持链式调用。
也就是像下面的代码那样:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const promise1 = new Promise((resolve, reject) => {
console.log(1);
resolve();
});
const promise2 = promise1.then(() => {
console.log(2);
});
const promise3 = promise2.then(() => {
console.log(3);
})
// or
new Promise((resolve, reject) => {
console.log(1);
resolve();
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
});
上面的代码打印顺序就是1,2,3。下面我们对代码来做一点改动:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20new Promise((resolve, reject) => {
console.log(1);
resolve();
})
.then((a) => {
console.log(2);
new Promise((resolve,reject) => {
console.log(3);
resolve();
})
.then((c) => {
console.log(4);
})
.then((d) => {
console.log(6);
})
})
.then((b) => {
console.log(5);
});
ok,结合前面理解的几条Promise A+规范,让我们来一起分析一下这段代码的执行顺序:
首先打印 1 ,后面跟着个
resolve
调用,说明 promise 的状态变为fulfilled
,所以把它的下一个 then 的回调即 a回调 放入微任务队列等待执行;根据规范2.2.7,then必须返回一个promise ,因此包含a回调的这个then返回了一个新的promise,我们记为promise1 ,注意此时a回调还没被执行,也就是这个promise1的状态还是pending
;根据规范2.2.2.1,then回调的执行必须在上一个promise的状态为fulfilled,所以下一个then,也就是b回调其实是被缓存在promise1内部的回调队列里,等promise1的状态改变再放入微任务队列。接着执行a回调,先打印2,然后接着往下执行,遇到了一个新的 promise,我们记为promise2, 接着先打印3,然后后面跟着个
resolve
调用,说明这个promise2的状态变为fulfilled
,所以把它的下一个 then 的回调也就是c回调放入微任务队列等待执行,同样根据规范2.2.7,包含c回调的这个then也返回了一个新的promise,我们记为promise3,此时c回调还没有执行,也就是这个promise3的状态还是pending
,同样根据规范2.2.2.1,所以下一个then,也就是d回调其实是被缓存在promise3内部的回调队列里,等promise3的状态改变再放入微任务队列。接着a回调执行完了,没有返回东西,可以理解为返回undefined ,根据规范2.3.4,如果 x 既不是对象也不是函数,用x完成(fulfill)promise,说明上面的promise1的状态变为了
fulfilled
,因此之前的b回调此时可以被放入微任务队列里等待执行了。经过上面的步骤,此时微任务队列里存在c回调和b回调。
接着先执行c回调,打印4,c回调执行完成没有问题,根据规范2.3.4,也就是上面说到的promise3的状态变为了
fulfilled
,此时d回调可以被放入微任务队列等待执行了。接着执行b回调,打印5。
接着执行d回调,打印6。
综上所述,打印顺序为1,2,3,4,5,6。
理解了上面的执行顺序,我们再来稍微改变一下上面的代码,我们给内部的这个 promise 添加一个 return,来看看打印顺序会不会发生改变。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20new Promise((resolve, reject) => {
console.log(1);
resolve();
})
.then((a) => {
console.log(2);
return new Promise((resolve,reject) => {
console.log(3);
resolve();
})
.then((c) => {
console.log(4);
})
.then((d) => {
console.log(6);
})
})
.then((b) => {
console.log(5);
});
根据规范2.3.2,如果 x
是一个promise,采用promise的状态,如果 x
是请求状态(pending), promise
(也就是这个then代表的promise)必须保持pending直到 x
fulfilled 或 rejected;如果 x
是完成态(fulfilled),用相同的值完成fulfill promise
;如果 x
是拒绝态(rejected),用相同的原因reject promise
。
我的理解:此时包含a回调的这个then返回了一个新的promise,再链式调用的话,相当于包含b回调的这个then是被跟在返回的这个新的promise上。因此上面的代码可以直接理解为下面的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20new Promise((resolve, reject) => {
console.log(1);
resolve();
})
.then((a) => {
console.log(2);
return new Promise((resolve,reject) => {
console.log(3);
resolve();
})
.then((c) => {
console.log(4);
})
.then((d) => {
console.log(6);
})
.then((b) => {
console.log(5);
});
})
换成这段代码,再让你说出输出顺序就没问题了吧?答案是1,2,3,4,6,5。
参考文章: