持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
前言
你真的完全知道 Promise 的执行顺序吗,如果你只通过 Promise A+ 规范了解 promise ,你一定不能完全理解 Promise,不信的话你来看看下面这道题
Promise.resolve()
.then(() => {
console.log(0);
return Promise.resolve(); // 如果注释这一行,结果又会是什么呢
})
.then(() => {
console.log(4);
});
Promise.resolve()
.then(() => {
console.log(1);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(5);
})
.then(() => {
console.log(6);
});
// 0 1 2 3 4 5 6
如果你不能清晰的说出这两种情况的过程,这篇文章一定对你有帮助。
在阅读本文之前,希望你了解:
- Promise 基本语法
- 事件循环模型
- 微任务
Promise.prototype.then
then 方法是一个 异步的 微任务,解析到这行代码的时候,会把 then 之后的 Promise 链条放到 微任务队列 中
⭐️then 的返回值
Promise.resolve().then(() => {}); // fulfilled
then 可以链式调用的原因是因为 then 方法可以返回一个 新 的 Promise 实例,注意是 新的实例,与 Promise 链条传递的 之前的 Promise 实例 并无多大关系,而且这个新的实例 状态 一定不是 pending,知道这些内容就足够了
⭐️控制 then 返回值
这里的内容才是重点:
then 处理返回值有两种情况:
- 不
return值(等同于return undefined) / 显示地return非promise的值, - 显示
return具有resolve的promise的值
如果是
return非resolve的值(排除reject情况),Promise链不会传递下去,所以这里不讨论这种情况
then 处理这两种情况决然不同,这里的观点你基本不会在任何书上看到,全部来自于 v8 引擎 内部实现
- 处理
return非promise的值
Promise.resolve().then(() => {});
这里相当于 return undefined,在被 抛出 返回值的时候,会被 Promise.resolve 包装,所以最后的 promise 状态为 fulfilled,这个包装的过程不发生在 return 的时候,而发生在整体代码执行完 抛出 返回值的过程 被包装。
也就是说你 不能理解 成这样:
Promise.resolve().then(() => { return Promise.resolve(undefined) })
- 处理
return具有resolve的promise的值
Promise1.resolve()
.then(() => Promise2.resolve());
这里的过程很复杂,分为三步:
- 执行 ResolvePromise (来源于 v8 引擎内部),调用
promiseResolveThenableJobTask(来源于 v8 引擎内部) 产生一个微任务放置到微任务队列,这个微任务就是一个函数
let promiseResolveThenableJobTask = () => {
p1.then((value) => {
ReslovePromise(p2, value) // 传递 promise
})
}
- 在主线程任务清空的情况下,取出并执行
promiseResolveThenableJobTask微任务,执行完这个任务也就是调用这个函数,会产生一个Promise2.fulfilled的微任务,这个微任务作用是将内部的Promise2的结果传递给then外部 - 主线程从微任务队列取出执行
Promise2.fulfilled任务,调用ReslovePromise函数 ,然后用Promise.resolve包装返回结果
所以,按照执行的顺序,在时间顺序上完全可以理解为插入了两个 你感受不到的微任务
也就是说,在时间顺序上落后处理 return 非 promise 两个 then 的时间,那么代码可以转换(并不能化等号)为
Promise1.resolve()
.then(() => {
return undefined;
})
.then((data) => data)
.then(data => data);
解题
再来看开头的那道题
没有添加 return Promise.resolve()
Promise.resolve() // p1
.then(() => { // p2
console.log(0);
})
.then(() => { // p3
console.log(4);
});
Promise.resolve() // p4
.then(() => { // p5
console.log(1);
})
.then(() => { // p6
console.log(2);
})
.then(() => { // p7
console.log(3);
})
.then(() => { // p8
console.log(5);
})
.then(() => { // p9
console.log(6);
});
// 0 1 4 2 3 5 6
为了简化过程,在代码注释的内容代替变量
执行顺序:
(1)执行完所有同步代码, p1 和 p2 的状态变为 fulfilled,并且先微任务队列中插入两个处理后续的 then 的微任务 [p1.fulfilled, p4.fulfilled]
(2)开始执行异步任务,从微任务队列中取出 p1.fulfilled 并执行,输出 0,微任务队列变成 [p4.fulfilled,p2.fulfiled]
(3)从微任务队列中取出 p4.fulfilled 并执行,输出 1,微任务队列变成 [p2.fulfilled,p6.fulfiled]
(4)从微任务队列中取出 p2.fulfilled 并执行,输出 4,执行完变成 p3.fulfilled,因为后面没有异步任务了,微任务队列变成 [p6.fulfiled]
(5)从微任务队列中取出 p6.fulfilled 并执行,输出 2,微任务队列变成 [p7.fulfiled]
(6) 。。。
所以你能看到两个 promise 链条 交替执行,输出的结果也就是交替的
添加 return Promise.resolve()
Promise.resolve() // p1
.then(() => { // p2
console.log(0);
return Promise.resolve(); // p0
})
.then(() => { // p3
console.log(4);
});
Promise.resolve() // p4
.then(() => { // p5
console.log(1);
})
.then(() => { // p6
console.log(2);
})
.then(() => { // p7
console.log(3);
})
.then(() => { // p8
console.log(5);
})
.then(() => { // p9
console.log(6);
});
// 0 1 2 3 4 5 6
为了简化过程,在代码注释的内容代替变量
执行顺序:
(1)执行完所有同步代码, p1 和 p2 的状态变为 fulfilled,并且先微任务队列中插入两个处理后续的 then 的微任务 [p1.fulfilled, p4.fulfilled]
(2)开始执行异步任务,从微任务队列中取出 p1.fulfilled 并执行,输出 0,看到返回值是个 Promise,会调用 NewPromiseResolveThenableJobTask 产生一个微任务 ,微任务队列变成 [p4.fulfilled,NewPromiseResolveThenableJobTask(p1.fulfilled)]
(3)从微任务队列中取出 p4.fulfilled 并执行,输出 1,微任务队列变成 [NewPromiseResolveThenableJobTask(p1.fulfilled),p5.fulfiled]
(4)从微任务队列中取出 NewPromiseResolveThenableJobTask(p1.fulfilled) 并执行,然后向微任务队列添加 p0.fulfilled 这个微任务(作用:将 p0 传递给 p2),微任务队列变为 [p6.fulfiled, p0.fulfilled]
(5)从微任务队列中取出 p5.fulfilled 并执行,输出 2 ,微任务队列变成 [p0.fulfilled, p6.fulfiled,]
(6)从微任务队列中取出 p0.fulfilled 并执行,将 p0 传递给 p3,微任务队列变成 [p6.fulfilled, p2.fulfiled,]
(7)从微任务队列中取出 p6.fulfilled 并执行,输出 3 ,微任务队列变成 [p2.fulfilled, p7.fulfiled,]
(8)从微任务队列中取出 p2.fulfilled 并执行,输出 4,执行完变成 p3.fulfilled,因为后面没有异步任务了,微任务队列变成 [p7.fulfiled]
(7)。。。后面的过程就不用多说了吧
如果你不想了解的那么细致,你直接将代码按照时间顺序将 return promise 替换为两个 then,因为这些时间执行了两个处理 promise 的 微任务
Promise.resolve()
.then(() => {
console.log(0);
return undefined;
})
.then(data => {
return data;
})
.then(data => {
return data;
})
.then(() => {
console.log(4);
});
按照 return 非 promise 的情况来执行,输出的代码为 0 -> 1 -> 空 -> 2 -> 空 -> 3 -> 4 -> 5 -> 6,依然是两个 promise 链条交替执行的结果
更复杂的情况
如果我们将 promise 链条后面的
then拿进来呢?
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve()
.then(() => {
console.log(4);
})
.then(() => {
console.log(7);
})
.then(() => {
console.log(8);
});
});
Promise.resolve()
.then(() => {
console.log(1);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(5);
})
.then(() => {
console.log(6);
});
// 0 1 4 2 7 3 8 5 6
执行顺序:
会在 promiseResolveThenableJobTask 处理 promise.fulfilled 之前,会将内部的 promise 链条放到微任务队列,执行完内部的 promise 链条,才会执行 promiseResolveThenableJobTask 后面的过程
所以,你也会看到交替执行的结果
Promise.resolve
上面的内容我们说 Promise.resolve 会包装 then 抛出 的结果,很多人认为 Promise.resolve 是new Promise((resolve) => resolve())语法糖,这种说法是完全错误的
const p = new Promise(() => {});
p === Promise.resolve(p); // true
p === new Promise(resolve => resolve(p)) // false
上面的代码可以很好的说明,两者最重要的区别,Promise.resolve 是幂等的,而且不会改变 promise 的状态
所以才能用 Promise.resolve 包装 then 的 return 结果并且 抛出
总结
promise.then 显示指定类型为 promise 作为返回值,会引起 js 解析引擎 额外的处理过程,在时间顺序上完全等同于插入 两个 then 的结果,你可以这样理解让这个过程变得更加简单
相信你看完本篇文章一定会加深对 promise 的理解,如果本篇文章对你有帮助或者你有不同的看法,欢迎在评论区留下你的足迹。