之前对于Promise并没有深度的了解过,闲来无事手写了Promise,过程中对Promise有了更进一步的了解,其中比较难的就在于then()的内部处理,之所以能实现链式调用和异步处理,最重要的核心就是一旦调用了then()方法,内部就会重启一个新的Promise并将其返回,并根据Promise的状态来做不一样的处理

...
then(){
const promsie = new MyPromise((resolve, reject) => {
...
}
return promsie;
}
...
// 新创建的Promise内部逻辑
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}

当Promise的内部状态变为fulfilledrejected时会创建一个微任务并调用成功或失败回调,否在就将回调函数缓存起来.

const fulfilledMicrotask = () =>  {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}

其中还有一个重要的地方就是resolvePromise方法,此方法会根据回调的返回值的类型做不同的处理,大概得分为两种情况

  1. x不是Promise,直接调用相关方法更改状态;
  2. x是Promise, 创建一个微任务,并将NewPromiseResolveThenableJobTask方法插入微任务内;而这个方法简单理解就是将返回的Promise链重新包装成一个Promise

当执行NewPromiseResolveThenableJob时进一步调用PromiseResolveThenableJob==>PerformPromiseThen==>PerformPromiseThenImpl,PerformPromiseThenImpl这个方法的内部有一个分支,这里不过多解释,后面示例中会详细说明,但是我们主要记住这个方法内部执行了NewPromiseFulfillReactionJobTask,创建了一次微任务,所以这个过程中创建了两次微任务,这个过程在前端代码中是无感的,所以在某些情况下,Promise的执行顺序就会产生一些特殊的结果.

//PerformPromiseThenImpl内部有了分支,即当前promise的状态判断执行逻辑
transitioning macro PerformPromiseThenImpl(implicit context: Context)(
promise: JSPromise, onFulfilled: Callable|Undefined,
onRejected: Callable|Undefined,
resultPromiseOrCapability: JSPromise|PromiseCapability|Undefined): void {
//pending状态时,将回调缓存,并在promise状态全部完成后推送至任务列队
if (promise.Status() == PromiseState::kPending) {
// The {promise} is still in "Pending" state, so we just record a new
// PromiseReaction holding both the onFulfilled and onRejected callbacks.
// Once the {promise} is resolved we decide on the concrete handler to
// push onto the microtask queue.
const handlerContext = ExtractHandlerContext(onFulfilled, onRejected);
const promiseReactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
const reaction = NewPromiseReaction(
handlerContext, promiseReactions, resultPromiseOrCapability,
onFulfilled, onRejected);
promise.reactions_or_result = reaction;
} else {
const reactionsOrResult = promise.reactions_or_result;
let microtask: PromiseReactionJobTask;
let handlerContext: Context;
if (promise.Status() == PromiseState::kFulfilled) {
handlerContext = ExtractHandlerContext(onFulfilled, onRejected);
microtask = NewPromiseFulfillReactionJobTask(
handlerContext, reactionsOrResult, onFulfilled,
resultPromiseOrCapability);
} else
deferred {
assert(promise.Status() == PromiseState::kRejected);
handlerContext = ExtractHandlerContext(onRejected, onFulfilled);
//调用NewPromiseFulfillReactionJobTask方法创建任务,
microtask = NewPromiseRejectReactionJobTask(
handlerContext, reactionsOrResult, onRejected,
resultPromiseOrCapability);
if (!promise.HasHandler()) {
runtime::PromiseRevokeReject(promise);
}
}
//将任务推送任务列队
EnqueueMicrotask(handlerContext, microtask);
}
promise.SetHasHandler();
}

下面根据一些例子详细的说明:

1.下面这道题应该是比较常见的一道题目了,最终的打印顺序是:0,1,2,3,4,5,6
Promise.resolve()
.then(() => { //promise_1A
console.log(0);
return Promise.resolve(4);
})
.then((res) => { //promise_Res
console.log(res)
})

Promise.resolve()
.then(() => { //promise_2A
console.log(1);
})
.then(() => { //promise_2B
console.log(2);
})
.then(() => { //promise_2C
console.log(3);
})
.then(() => { //promise_2D
console.log(5);
})
.then(() =>{ //promise_2E
console.log(6);
})

逐行分析:

  1. Promise.resolve()等价于new Promise(resolve=>{resolve()}),遇到第一个then(),创建一个Promise(命名promise_1A,状态pending),由于前一个Promsie状态是fulfilled,直接创建一个微任务,将回调函数放入其中;任务入列

    //加入微任务内
    () => {
    console.log(0);
    return Promise.resolve(4);
    }
  2. 遇到第二个then,此时前置位Promise(promise1_A)状态为pending,创建一个Promise(命名promise_Res,状态pending),将回调函数缓存至本地.

  3. 第二个Promsie.resolve同理,再遇到第一个then()时,创建一个Promise(命名promise_2A,状态pending),创建一个微任务并入列;

    //加入微任务内
    () => {
    console.log(1);
    }
  4. 后面的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)]

  5. 继续执行任务Job(promise_2A),打印1;回调执行完毕,返回值为undefined,直接调用方法修改promsie(promise_2A)状态为fulfilled;创建微任务并promise_2B的回调加入微任务;此时一进一出,任务列队有两个任务[Job(NewPromiseResolveThenableJobTask),Job(promise_2B)]
  6. 执行NewPromiseResolveThenableJobTask方法,此时内部调用PerformPromiseThenImpl方法,这里就来到了重要的阶段,此时判断的status就是我们重新包裹的promise即Promise.resolve(4),由于Promise.resolve(4)状态是fulfilled所以创建一个微任务并入列,一进一出,此时任务列队[Job(promise_2B),microtask(NewPromiseFulfillReactionJobTask)]
  7. 执行Job(promise_2B),打印2,回调执行完毕,返回undefined,修改状态,创建微任务并将promise_2C的回调加入,此时任务列队[Job(NewPromiseFulfillReactionJobTask),Job(promise_2C)];
  8. 执行Job(NewPromiseFulfillReactionJobTask),执行完毕后,promise_1A的状态变为fulfilled,创建一个新的微任务,并将promise_Res的回调加入.此时一进一出,任务列队[Job(promise_2C),Job(promise_Res)];
  9. 执行Job(promise_2C),打印3,Job(promsie_2D)入列
    [Job(promise_Res),Job(promise_2D)]
  10. 执行Job(promise_Res),打印4,此时第一个promise链结束;Job(promise_2E)入列,[Job(promise_2D),Job(promise_2E)]
  11. 依次打印5,6


2. 修改下上面题目的细节,最终的打印顺序是:0,1,新增的then,2,3,4,5,6
Promise.resolve()
.then(()=>{ //promise_1A
console.log(0);
return Promise.resolve(4)
.then((res) => { promise_新
console.log("新增then");
return res;
});
})
.then((res)=>{ //promise_Res
console.log(res);
});

Promise.resolve()
.then(()=> { //promise_2A
console.log(1);
})
.then(() => { //promise_2B
console.log(2);
})
.then(() => { //promise_2C
console.log(3);
})
.then(() => { //promise_2D
console.log(5);
})
.then(() => { //promise_2E
console.log(6);
});

和上题的区别是,运行到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(function a() { //promise_1A
console.log(0);
return Promise.resolve(4)
.then((res) => { //promise_新1
console.log("第一个新增then!");
return res;
})
.then((res) => { //promise_新2
console.log("第二个新增then");
return res;
})
})
.then((res) => { //promise_Res
console.log(res);
});

Promise.resolve()
.then(() => {//promise_2A
console.log(1);
})
.then(() => { //promise_2B
console.log(2);
})
.then(() => { //promise_2C
console.log(3);
})
.then(() => { //promise_2D
console.log(5);
})
.then(() => { //promise_2E
console.log(6);
});

和上面的区别是我们在内部又添加了一个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

总结

  1. then内部创建了新的Promise,只用当前一个Promsie状态为fulfilled时,才会将回调包裹在微任务内并排队,否则缓存起来;
  2. 当回调返回除了Promise以外的值时,直接调用resolve相关方法并修改状态;
  3. 当回调返回的是Promise时,创建一个新的微任务并将NewPromiseResolveThenableJob方法放入其中;
  4. 执行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
// PromiseReaction holding both the onFulfilled and onRejected callbacks.
// Once the {promise} is resolved we decide on the concrete handler to
// push onto the microtask queue.

参考文章:

  1. https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/promise-jobs.tq#13
  2. https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/promise-abstract-operations.tq
  3. https://chromium.googlesource.com/v8/v8.git/+/refs/heads/8.4-lkgr/src/builtins/promise-abstract-operations.tq
  4. https://tc39.es/ecma262/#sec-promise-constructor
  5. https://juejin.cn/post/6945319439772434469#heading-20
  6. https://www.zhihu.com/question/453677175
  7. https://zhuanlan.zhihu.com/p/264944183
  8. https://zhuanlan.zhihu.com/p/329201628