前言
先看一段代码
let p1 = Promise.resolve();
// 1. p1 的状态为 fulfilled
let p2 = p1.then(() => {
console.log(0);
let p3 = Promise.resolve(4);
return p3;
});
// 2. 因为 p1 的状态已经是 fulfilled,所以调用 then 后立即将 onFulfilled 放入 microtask 队列
// 此时 microtask 只有p1的 onFulfilled: [p1.onFulfilled]
let p4 = p2.then((res) => {
console.log(res);
});
// 3. p2的状态还是 pending,所以调用 then 后是为 p2 收集依赖,
// 此时 p2 的 reactions 如下
/*{
onFulfilled: (res) => {console.log(res)},
onRejected: undefined
}*/
let p5 = Promise.resolve();
// 4. p5 的状态为 fulfilled
let p6 = p5.then(() => {
console.log(1);
});
// 5. 同第2步,将 onFulfilled 加入 microtask 队列
// 此时 microtask 是: [p1.onFulfilled, p5.onFulfilled]
let p7 = p6.then(() => {
console.log(2);
});
// 6. 同第3步,是给 p6 添加 reactions
let p8 = p7.then(() => {
console.log(3);
});
// 7. 同上,是给 p7 添加 reactions
let p9 = p8.then(() => {
console.log(5);
});
// 8. 同上,是给 p8 添加 reactions
let p10 = p9.then(() => {
console.log(6);
});
// 9. 同上,是给 p9 添加 reactions
代码中已经增加了注释,其中出处来自:
知乎- Promise.then
问题点
如果按照正常流程来看, 由于Promise的状态改变,当其为fulfilled
时,会将then
的 onFulfilled
回调加入到微任务队列中,那么代码执行完成后, 微任务队列中就存在 p2.onFulfilled
以及p6.onFulfilled
两个任务。
如果p2
中的回调是正常返回值或者没有返回值,默认底层会返回一个新的Promise
给后续的then
处理,那么此时顺序应该是:0 1 4 2 3 5 6
, 但代码最终输出的是 0 1 2 3 4 5 6
但由于p2
的执行,内部返回的是一个Promise.resolve
, 由知乎文章内大佬们的解释可得知,最终这块代码会被解释为:
let promiseResolveThenableJobTask = () => {
p3.then((value) => {
ReslovePromise(p2, value)
})
}
并重新被推入到队列中,所以队列的变化是这样是:
[p2.onFulfiled,p6.onFulfilled] => [p6.onFulfilled,promiseResolveThenableJobTask ]
此时输出了0,接着p6被解析输出了1,此时Promise被处理交由下一个p7处理,此时队列变为
[promiseResolveThenableJobTask,p7.onFulfilled]
被包装的promiseResolveThenableJobTask,最终执行的是前面解释的代码p3.then,那么接着队列中由重新推入了一个任务
ReslovePromise(p2, value),并且value此时为4
[p7.onFulfilled,ReslovePromise(p2, value)]
接着执行p7输出2,此时任务交给ReslovePromise,它再次执行p2本身并传入value,此时实际才会交给到p4的执行,此时又再次进入队列,并且p7执行完后交给下一个then执行
[p8.onFulfilled,p4.onFulfiled]
此时输出3,然后输出4
总结
由于返回的是一个新的Promise.resovle
,在ECMA标准中,它会被包装成一个新的函数执行,并在上一个微任务执行时,作为新的微任务推入到队列,最终会导致产生两次微任务才会流转到下一个then
的调用