Promise 的状态吸收

Huy大约 4 分钟javascriptjavascript

一直以来,对 Promise 的状态吸收问题,我都没有很好理解,今天终于理顺了,因此记录一下。

由于 Promise 规范中对这块没有详细定义,因此这里的 Promise 处理机制均是基于 V8 引擎的。

Promise 回顾

Promise 有三种状态:pendingfulfilledrejected。当使用 resolvereject 时,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 吸收机制来理解。

  1. 首先定义了俩个 Promise,p1 和 p2,但是 P2 需要等待 P1 的状态变化,因此 p2 的状态是 pending。

  2. P1 首先变化进入到 fulfilled 状态,值为 p1。因此打印 p1 0,然后 p2 吸收 p1 的状态,因此 p2 的状态也变成了 fulfilled,值为 p1

  3. 由于 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

解析:

  1. 首先执行 p1,打印 1,然后执行 p2,打印 3,然后 p2 返回一个 Promise,p1 等待这个 Promise,因此会进入微任务队列。
  2. 后面俩个 Promise.resolve() 会依次进入微任务队列, 依次打印 57 8。此时 p2 状态完成,p1 的 await 吸收 p2 的结果,但还需要进入微任务等待一次。
  3. 最后俩个 Promise.resolve() 会依次进入微任务队列, 依次打印 6undefined 9。此时 p1 的 await 状态完成,打印 2

整体结果会有点绕,但理解了会很清晰。

Loading...