最近看到一个 Promise 相干的很有意思的代码:

new Promise((resolve) => {  console.log(1)  resolve()}).then(() => {  new Promise((resolve) => {    console.log(2)    resolve()  }).then(() => {    console.log(4)  })}).then(() => {  console.log(3)})

第一次看到这个代码的时候,认为的输入后果会是:1,2,3,4,然而被理论的输入后果打脸 。

如图所示,理论的输入后果为:1,2,4,3

代码剖析

为了搞清楚理论的输入后果为什么是:1,2,4,3,咱们来一步步剖析代码的执行。

咱们晓得,Promise 实例化时,传入的回调会立刻执行,而Promise 的 then 回调会被放到微工作队列中,期待执行。队列就是一个先进先出的列表,先被放到队列的回调,会被优先执行。后面的代码中,一共有 5 个回调函数。

回调1 是 Promise 实例化时的回调,所以会立刻执行,此时控制台打印出数字 1,而后 resolve() 办法被调用,此时的 Promise 状态被批改成了 fulfilled(如果没有调用 resolve() 办法,Promise 的状态为 pending)。

Promise 实例化实现后,第一个 then() 办法被调用, 回调2 会被放入了微工作队列中,期待执行。

then 办法何时调用?

这个时候疑难点来了,第一个 then() 办法被调用后,第二个 then 办法会不会马上被调用,如果会,那输入的后果就应该是 :1,2,3,4。显然,此时不会马上调用第二个 then() 办法,也就是不会马上将 回调5 放入微工作队列。那如果不会,那何时才会被调用?

这个时候,须要看一下 Promise/A+ 标准。重点是上面几条:

2.2 then 办法
promise 的 then 办法承受两个参数:

promise.then(onFulfilled, onRejected)

2.2.2 如果 onFulfilled 是函数:

  • 2.2.2.1 当 promise 处于已解决状态时,该函数必须被调用并将 promise 的值作为第一个参数。
  • 2.2.2.2 该函数肯定不能在 promise 处于已解决状态之前调用。
  • 2.2.2.3 该函数被调用次数不超过一次。

2.2.6 then 能够在同一个 promise 上屡次调用。

  • 2.2.6.1 如果 promise 处于已解决状态时,所有相应的 onFulfilled 回调必须依照它们对 then 的组织程序顺次调用。
  • 2.2.6.2 如果 promise 处于已回绝状态时,所有相应的 onRejected 回调必须依照它们对 then 的组织程序顺次调用。

2.2.7 then 必须返回一个 promise。

promise1 = new Promise(resolve => resolve())// promise1 能够屡次调用 then// 且 onFulfilled 回调的执行程序,依照 .then 的调用程序执行promise1.then(onFulfilled1) // 1promise1.then(onFulfilled2) // 2promise1.then(onFulfilled3) // 3// 下面 3 个 onFulfilled,依照 1、2、3 的程序执行
// 调用 .then 办法后,返回一个新的 promisepromise2 = promise1.then(onFulfilled, onRejected);

综上,第一个 then() 办法调用后,会返回一个新的 Promise。这样做的目标就是为了放弃链式调用,而且 then() 办法内的 onFulfilled 回调会期待 Promise 状态批改之后才会调用。

咱们略微批改一下后面代码的调用模式,如下:

const p1 = new Promise((resolve) => {  console.log(1)  resolve()})const p2 = p1.then(() => {  new Promise((resolve) => {    console.log(2)    resolve()  }).then(() => {    console.log(4)  })})const p3 = p2.then(() => {  console.log(3)})

p1.then() 会返回一个新的 Promise 命名为 p2,前面的 p2.then() 的回调会在 p1.then() 内的回调函数执行完之后,才会调用,也就是 p2 这个 Promise 状态产生扭转之后。

所以,只有 回调2 执行实现后,才会执行 p2.then()。咱们再看 回调2 的内容。

回调2 先是对一个 Promise 进行了实例化操作,实例化的回调为 回调3 ,该回调会立刻执行,此时控制台打印出数字 2,而后 resolve() 办法被调用,此时的 Promise 状态被批改成了 fulfilled,前面的 回调4 会放入微工作队列。回调2 执行结束后,执行 p2.then()回调5 被放入微工作队列。

依照队列先进先出的执行程序,先执行 回调4,而后执行 回调5。所以,在控制台会先输入数字 4,而后输入数字 3

如果想要输入的后果为:1,2,3,4,能够将代码改成如下模式:

const p1 = new Promise((resolve) => {  console.log(1)  resolve()})p1.then(() => {  new Promise((resolve) => {    console.log(2)    resolve()  }).then(() => {    console.log(4)  })})p1.then(() => {  console.log(3)})

依据后面的 2.2.6 规定,then 能够在同一个 promise 上屡次调用,且 p1 前面的 then 会依照他们的调用程序间接放入微工作队列中。