Promise 的状态吸收
一直以来,对 Promise 的状态吸收问题,我都没有很好理解,今天终于理顺了,因此记录一下。
由于 Promise 规范中对这块没有详细定义,因此这里的 Promise 处理机制均是基于 V8 引擎的。
Promise 回顾
Promise 有三种状态:pending
、fulfilled
、rejected
。当使用 resolve
或 reject
时,Promise 的状态会发生变化,但一旦状态发生变化,就不会再变。
而 Promise 的吸收是指当 resolve 一个 Promise 时,会吸收该 Promise 的状态,并返回一个新的 Promise。例如在链式调用中,一个 Promise 的解决值可能是另一个 Promise,此时外层的 Promise 会等待内部 Promise 的状态变化,从而同步自己的状态。这通常被称为“Promise 的吸收”。
如果外层是 reject 一个 Promise,则会直接返回一个 rejected 状态的 Promise,而不会吸收内部 Promise 的状态。
例如:
const innerPromise = new Promise((resolve) =>
setTimeout(() => resolve(42), 1000)
)
const outerPromise = new Promise((resolve) => {
resolve(innerPromise) // 传入一个 Promise
})
outerPromise.then((value) => {
console.log(value) // 1秒后输出 42
})
这个新 Promise 的状态将由内部的 Promise.resolve(42)
决定,即最终变为 fulfilled
,值为 42。这种情况下,外层 Promise“吸收”了内部 Promise 的状态。
额外思考🤔: 为什么需要状态吸收呢?
其实,反过来想,如果直接让外层 Promise 进入到 fulfilled 状态(值为内层 Promise 对象),这样会导致 Promise 的链式调用(
.then()
)需要用户手动处理嵌套的 Promise。即用户需要自己造轮子,从而破坏链式调用的简洁性。状态吸收使得我们,无需手动解包嵌套的 Promise。
如果是套娃循环引用呢? 如果 Promise A 的解决依赖于 Promise B,而 Promise B 的解决又依赖于 Promise A,V8 会检测到循环引用并抛出
TypeError
。
Promise 的吸收机制
能够理解上文,接下来就看 Promise 链中的状态传递了。先看一个例子:
const p1 = new Promise((resolve, reject) => {
resolve('p1')
})
const p2 = new Promise((resolve, reject) => {
resolve(p1)
})
p1.then((res) => {
console.log(res, 0)
})
.then((res) => {
console.log(res, 1)
})
.then((res) => {
console.log(res, 2)
})
.catch((err) => {
console.log(err, 3)
})
p2.then((res) => {
console.log(res, 4)
})
.then((res) => {
console.log(res, 5)
})
.then((res) => {
console.log(res, 6)
})
.catch((err) => {
console.log(err, 7)
})
这个其实有点难了,需要结合上面的 Promise 吸收机制来理解。
首先定义了俩个 Promise,p1 和 p2,但是 P2 需要等待 P1 的状态变化,因此 p2 的状态是 pending。
P1 首先变化进入到 fulfilled 状态,值为 p1。因此打印
p1 0
,然后 p2 吸收 p1 的状态,因此 p2 的状态也变成了 fulfilled,值为p1
。由于 p1 先执行,因此接下来 p1 和 p2 的链式调用会同时依次执行。
// 依次打印 p1 0 undefined 1 p1 4 undefined 2 undefined 5 undefined 6
深入理解
可以思考一下 🤔,如果 p1 是 rejected 状态呢?或者 p2 是 rejected 状态呢?
在开头讲到,如果外层是 reject 一个 Promise,则会直接返回一个 rejected 状态的 Promise,而不会吸收内部 Promise 的状态。因此,如果 p1 是 rejected 状态,那么 p2 的状态也会是 rejected 状态。
const p1 = new Promise((resolve, reject) => {
reject('p1') // 这里reject了
})
const p2 = new Promise((resolve, reject) => {
resolve(p1)
})
p1.then((res) => {
console.log(res, 0)
})
.then((res) => {
console.log(res, 1)
})
.then((res) => {
console.log(res, 2)
})
.catch((err) => {
console.log(err, 3) // 依次到这里reject --> p1 3
})
p2.then((res) => {
console.log(res, 4)
})
.then((res) => {
console.log(res, 5)
})
.then((res) => {
console.log(res, 6)
})
.catch((err) => {
console.log(err, 7) // p2 继承了p1的reject --> p1 3
})
上面结果是:
p1 3
p1 3
如果 p2 是 rejected 状态,那么就不会继承 p1 的状态,还是保持 rejected。
const p1 = new Promise((resolve, reject) => {
resolve('p1')
})
const p2 = new Promise((resolve, reject) => {
reject(p1) // 这里reject了
})
p1.then((res) => {
console.log(res, 0) // p1 0
})
.then((res) => {
console.log(res, 1) // undefined 1
})
.then((res) => {
console.log(res, 2) // undefined 2
})
.catch((err) => {
console.log(err, 3)
})
p2.then((res) => {
console.log(res, 4)
})
.then((res) => {
console.log(res, 5)
})
.then((res) => {
console.log(res, 6)
})
.catch((err) => {
console.log(err, 7) // p2 继承了一个 fulfilled 状态的p1 --> Promise { 'p1' } 7
})
需要注意的是,Promise 的链式调用是交替依次进行的。这里不过多展开。
总结
resolve()
传入 Promise 时,必须等这个 Promise 执行完,才能决定自己的状态。
🔥 口诀:
resolve(Promise)
== 状态跟随;resolve(p1)
会等待 p1 的状态结果,然后传递。resolve(值)
== 直接成功;reject(Promise)
== 直接抛出对象本身。reject(p1)
只是直接把 p1 当成错误原因,不关心状态。
挑战
如果对上文理解清楚了,可以试试下面 👇🏻 这道题,看看能不能一次性做对。有点难度 🤯
async function p1() {
console.log(1)
await p2()
console.log(2)
}
async function p2() {
console.log(3)
return Promise.resolve(4)
}
p1()
Promise.resolve()
.then(() => {
console.log(5)
})
.then(() => {
console.log(6)
})
new Promise((resolve) => {
resolve(7)
})
.then((res) => {
console.log(res, 8)
})
.then((res) => {
console.log(res, 9)
})
🎯 其结果是:
1
3
5
7 8
6
undefined 9
2
解析:
- 首先执行 p1,打印
1
,然后执行 p2,打印3
,然后 p2 返回一个 Promise,p1 等待这个 Promise,因此会进入微任务队列。 - 后面俩个 Promise.resolve() 会依次进入微任务队列, 依次打印
5
和7 8
。此时 p2 状态完成,p1 的 await 吸收 p2 的结果,但还需要进入微任务等待一次。 - 最后俩个 Promise.resolve() 会依次进入微任务队列, 依次打印
6
和undefined 9
。此时 p1 的 await 状态完成,打印2
。
整体结果会有点绕,但理解了会很清晰。