之前对于Promise并没有深度的了解过,闲来无事手写了Promise,过程中对Promise有了更进一步的了解,其中比较难的就在于then()
的内部处理,之所以能实现链式调用和异步处理,最重要的核心就是一旦调用了then()
方法,内部就会重启一个新的Promise并将其返回,并根据Promise的状态来做不一样的处理
... |
// 新创建的Promise内部逻辑 |
当Promise的内部状态变为fulfilled
或rejected
时会创建一个微任务并调用成功或失败回调,否在就将回调函数缓存起来.const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
其中还有一个重要的地方就是resolvePromise
方法,此方法会根据回调的返回值的类型做不同的处理,大概得分为两种情况
- x不是Promise,直接调用相关方法更改状态;
- x是Promise, 创建一个微任务,并将
NewPromiseResolveThenableJobTask
方法插入微任务内;而这个方法简单理解就是将返回的Promise链重新包装成一个Promise
当执行NewPromiseResolveThenableJob
时进一步调用PromiseResolveThenableJob==>PerformPromiseThen==>PerformPromiseThenImpl
,PerformPromiseThenImpl
这个方法的内部有一个分支,这里不过多解释,后面示例中会详细说明,但是我们主要记住这个方法内部执行了NewPromiseFulfillReactionJobTask
,创建了一次微任务,所以这个过程中创建了两次微任务,这个过程在前端代码中是无感的,所以在某些情况下,Promise的执行顺序就会产生一些特殊的结果.
//PerformPromiseThenImpl内部有了分支,即当前promise的状态判断执行逻辑 |
下面根据一些例子详细的说明:
1.下面这道题应该是比较常见的一道题目了,最终的打印顺序是:0,1,2,3,4,5,6
Promise.resolve() |
逐行分析:
Promise.resolve()
等价于new Promise(resolve=>{resolve()})
,遇到第一个then(),创建一个Promise
(命名promise_1A,状态pending),由于前一个Promsie
状态是fulfilled
,直接创建一个微任务,将回调函数放入其中;任务入列//加入微任务内
() => {
console.log(0);
return Promise.resolve(4);
}遇到第二个then,此时前置位Promise(promise1_A)状态为
pending
,创建一个Promise
(命名promise_Res,状态pending),将回调函数缓存至本地.第二个
Promsie.resolve
同理,再遇到第一个then()时,创建一个Promise
(命名promise_2A,状态pending),创建一个微任务并入列;//加入微任务内
() => {
console.log(1);
}后面的then依次类推,都分别创建Promise并缓存至本地(promise_2B,promise_2C,promise_2D,promise_2E),此时任务列队里有两个任务
[Job(promsie_1A),Job(promise_2A)]
,执行Job(promsie_1A)
,打印0;return Promise.resolve(4)
,创建新的promsie,状态为fulfilled;结束回调,返回值为Promise,创建一个微任务并将NewPromiseResolveThenableJobTask
方法放入, 一进一出,此时任务列队为[Job(promise_2A),Job(NewPromiseResolveThenableJobTask)]
- 继续执行任务Job(promise_2A),打印1;回调执行完毕,返回值为undefined,直接调用方法修改promsie(promise_2A)状态为fulfilled;创建微任务并promise_2B的回调加入微任务;此时一进一出,任务列队有两个任务
[Job(NewPromiseResolveThenableJobTask),Job(promise_2B)]
- 执行
NewPromiseResolveThenableJobTask
方法,此时内部调用PerformPromiseThenImpl
方法,这里就来到了重要的阶段,此时判断的status就是我们重新包裹的promise即Promise.resolve(4),由于Promise.resolve(4)状态是fulfilled所以创建一个微任务并入列,一进一出,此时任务列队[Job(promise_2B),microtask(NewPromiseFulfillReactionJobTask)]
- 执行Job(promise_2B),打印2,回调执行完毕,返回undefined,修改状态,创建微任务并将promise_2C的回调加入,此时任务列队
[Job(NewPromiseFulfillReactionJobTask),Job(promise_2C)]
; - 执行Job(NewPromiseFulfillReactionJobTask),执行完毕后,promise_1A的状态变为fulfilled,创建一个新的微任务,并将promise_Res的回调加入.此时一进一出,任务列队
[Job(promise_2C),Job(promise_Res)]
; - 执行Job(promise_2C),打印3,Job(promsie_2D)入列
[Job(promise_Res),Job(promise_2D)]
- 执行Job(promise_Res),打印4,此时第一个promise链结束;Job(promise_2E)入列,
[Job(promise_2D),Job(promise_2E)]
- 依次打印5,6
2. 修改下上面题目的细节,最终的打印顺序是:0,1,新增的then,2,3,4,5,6
Promise.resolve() |
和上题的区别是,运行到Promise.resolve(4).then
时,由于Promise.resolve(4)
对应的状态已经是fulfilled,所以会优于NewPromiseResolveThenableJobTask
加入任务列队;当我们运行NewPromiseResolveThenableJobTask
时,内部的Promise.resolve(4).then
状态已经是fulfilled
,任务列队情况如下:[Job(promise_1A),Job(promise_2A)]=>打印0
[Job(promise_2A),Job(promise_新),Job(NewPromiseResolveThenableJob)]=>打印1
[Job(promise_新),Job(NewPromiseResolveThenableJob),Job(promsie_2B)]=>打印新增then
[Job(NewPromiseResolveThenableJob),Job(promsie_2B)]=> 此时内部Promise.resolve(4).then状态fulfilled,插入新的微任务
[Job(promsie_2B),Job(NewPromiseFulfillReactionJobTask)]=>打印2
[Job(NewPromiseFulfillReactionJobTask),Job(promsie_2C)]=>
[Job(promsie_2C),Job(promsie_Res)]=>打印3
[Job(promsie_Res),Job(promsie_2D)]=>打印4
[Job(promsie_2D),Job(promsie_2E)]=>打印5
[Job(promsie_2E)]=>打印6
3. 再次修改下上面题目的细节,最终的打印顺序是:0,1,第一个新增then,2,第二个新增then,3,5,4,6
Promise.resolve() |
和上面的区别是我们在内部又添加了一个then,此时后面的打印结果发生了变化,这个时候就是我们的另一种情况PerformPromiseThenImpl
内部执行的时候,内层包裹的promise链状态此时还是pending
状态,此时不会立即创建一个微任务,而是等待内部promise链的最终状态变为fulfilled
后,才会再次创建一个微任务并入列,任务列队情况如下:[Job(promise_1A),Job(promise_2A)]=> 打印0
[Job(promise_2A),Job(promise_新1),NewPromiseResolveThenableJob()]=> 打印1,因为内部第一个新增then状态还是peding,所以第二个新增then缓存本地
[Job(promise_新1),NewPromiseResolveThenableJob(),Job(promsie_2B)]=> 打印第一个新增then
[NewPromiseResolveThenableJob(),Job(promsie_2B),Job(promise_新2)]=>此时内部promsie链状态pending,所以将回调缓存等待
[Job(promsie_2B),Job(promise_新2)]=> 打印2
[Job(promise_新2),Job(promsie_2C)]=> 打印第二个新增then
[Job(promsie_2C),PerformPromiseThenImpl()]=>打印3,此时内部promise状态完成,创建一个新的微任务并入列
[PerformPromiseThenImpl(),Job(promsie_2D)]=>
[Job(promsie_2D),Job(promise_Res)]=>5
[Job(promise_Res),Job(promsie_2E)]=>4
[Job(promsie_2E)]=>6
总结
- then内部创建了新的Promise,只用当前一个Promsie状态为fulfilled时,才会将回调包裹在微任务内并排队,否则缓存起来;
- 当回调返回除了Promise以外的值时,直接调用resolve相关方法并修改状态;
- 当回调返回的是Promise时,创建一个新的微任务并将
NewPromiseResolveThenableJob
方法放入其中; - 执行
NewPromiseResolveThenableJob
时,底层方法PerformPromiseThenImpl
会根据包裹promise链的状态执行不同的分支,状态fullfilled时,调用NewPromiseFulfillReactionJobTask
创建一个新的微任务,状态为pending时,将回调缓存直到包裹的promise链状态完成后再创建微任务
说明:PerformPromiseThenImpl
中,当状态为pending时,根据源码内的注释,我们可以得出结论,直到这个promise状态变为完成后,会将它推入微任务列队,但是在实际执行中只看到返回了PromiseReaction对象用于记录回调函数,没找到具体在何时推入了微任务列队,不过在一些示例的推演中发现在内部promise全部结束后,只有插入一条微任务后,才能符合实际的打印顺序,所以目前暂时可以按照以上结论来理解,如果后续对此有明确结论.会再次修改本篇文章
// The {promise} is still in "Pending" state, so we just record a new |
参考文章:
- https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/promise-jobs.tq#13
- https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/promise-abstract-operations.tq
- https://chromium.googlesource.com/v8/v8.git/+/refs/heads/8.4-lkgr/src/builtins/promise-abstract-operations.tq
- https://tc39.es/ecma262/#sec-promise-constructor
- https://juejin.cn/post/6945319439772434469#heading-20
- https://www.zhihu.com/question/453677175
- https://zhuanlan.zhihu.com/p/264944183
- https://zhuanlan.zhihu.com/p/329201628