深入浅出Promise的宏任务与微任务

449 阅读4分钟

阅读本文章,默认你已经学会 Promise ,宏和微任务等概念。不会就赶紧去学吧。

先举个栗子

对于一处于pending状态的Promise对象p,内部状态的resolve,会让p.then(fn)中的fn加入微任务队列

//code 1
let p = new Promise(function f1(resolve) {
  setTimeout(function f2(){ 
    resolve(2) 
    console.log(1)
  }, 1000)
})
p.then(function f3(v) { console.log(v) })

在上面的代码中,f1 是同步执行的代码,在执行时创建一个定时器( f2 会加入定时器),之后执行 p.then ,此时 f3 并没有立即加入微任务队列。1秒后f2 执行时,运行 resolve(2),此时才触发 f3 加入微任务队列。

  • 运行同步代码 f1 ,创建定时器,初始化 p.then
  • 1秒后运行 f2 ,f2 加入宏任务队列。此时宏任务队列:[f2],微任务队列:[].
  • 先扫描微任务队列(为空),再扫描宏任务队列(拿出一个任务 f2 ),运行 f2 。遇到 resolve ,把 p.then 的 f3 移入微任务队列,之后输出1。此时宏队列:[],微队列[f3]
  • 执行完宏任务,再次扫描微任务队列,依次拿出并执行全部任务,执行 f3 输出2。

最后的结果是1秒后输出1,2。


再举个栗子

//code 2
let p1 = new Promise(function f1(resolve1) {
  setTimeout(resolve1)
})
let p2 = p1.then(function f2(v) { console.log(2) })
let p3 = p2.then(function f3(v) { console.log(3) })

let p11 = new Promise(function f11(resolve2) {
  setTimeout(resolve2)
})
let p22 = p11.then(function f22(v) { console.log(22) })
let p33 = p22.then(function f33(v) { console.log(33) })

对于 Promise 我们需要知道,链式调用 .then 之后会返回一个新的 Promise 对象。以上代码的执行流程是

  • 先执行同步代码。运行 f1,立即得到一个 Pending 状态的 Promise 对象 p1 ( f1 里面的 resolve1 函数被加入宏任务,还没开始执行)。运行 p1.then 得到一个 Pending 状态的 Promise 对象 p2 。 ......,同理你得到一个 pending 状态的 Promise 对象 p33。此时宏队列里有 [resolve1,resolve2]
  • 因为微队列目前为空,所以扫描宏队列,拿出 resolve1 运行,导致 p1 被 resolve(从 pending 变成了 fullfilled),从而导致 p1.then(f2)被加入了微队列。此时宏任务队列[resolve2],微任务队列[f2]。
  • 宏任务执行完,扫描微任务队列,执行 f2 ,输出2。p3 因为 p2 的状态 pending 变化成 fullfilled,导致 p2.then(f3)中的 f3 加入微任务队列。此时宏任务队列[resolve2],微任务队列[f3]。微任务队列不为空,拿出 f3,执行 f3 ,输出3,此时 p3 变为 fullfilled 状态,微任务队列为空。
  • 扫描下一个宏任务,拿出 resolve2 ,运行,导致 p11 被 resolve,导致 p11.then(f22)中的 f22 被加入微任务队列。此时宏队列[],微任务队列[f22]
  • 扫描全部的微任务,执行 f22,输出 22 。f22 运行结束,触发 p22 的状态从 pending 变成 fullfilled ,导致 p22.then 加入微任务队列。此时宏队列[],微队列[f33]。此时宏队列[],微队列[f33]。微任务队列还未扫描完,拿出f33,运行输出33,此时p33变成fulfilled状态。微队列为[]。

最终输出顺序为 2、3、22、33。以上代码等价于常见链式写法

new Promise( resolve => setTimeout(resolve) )
  .then( v => console.log(2) )
  .then( v => console.log(3) )

new Promise( resolve => setTimeout(resolve) )
  .then( v => console.log(22) )
  .then( v => console.log(33) )

再来个栗子

对于一处于fulfilled状态的Promise对象p,p.then(fn)会立即让fn加入微任务队列

// code 3
let p1 = Promise.resolve(1)
let p2 = p1.then(function f2() {
  console.log(2)
})
let p3 = p2.then(function f3() {
  console.log(3)
})

let p11 = new Promise(function f11(resolve) {
  resolve(11)
})
let p22 = p11.then(function f22() {
  console.log(22)
})
let p33 = p22.then(function f33() {
  console.log(33)
})

看起来和 code2 差不多,但是执行起来会得到不同的结果。分析一下具体的流程。

  • p1 = Promise.resolve()。创建了一个 Promise 对象 p1,其内部状态为 fullfilled。
  • p2 = p1.then(f2)。对于一处于 fullfilled 状态的 Promise 对象 p1,会立即让 f2 加入微任务队列( f2 并未执行),创建的 p2 是 pending。此刻微队列:[f2]
  • p3 = p2.then(f3).对于一处于 pending 状态的 Promise 对象 p2 ,p2的状态 resolve 才会让 f3 加入微队列。
  • p11 创建了一个 Promise 对象,内部状态为 fullfilled。
  • p22 = p11.then(f22).对于一个处于 fullfilled 状态的 Promise 对象 p11,会立即让 f22 加入微任务队列,创建的 p22 是 pending 的状态。此刻的微队列:[ f2 , f22 ]
  • 对于一个处于 pending 状态的 p22,f33 目前不会加入微队列中。
  • 扫描微任务队列
  • 拿出 f2 ,运行输出 2.。输出完毕,状态变为 fullfilled,将 f3 加入微队列中。此时微队列:[ f22, f3 ]
  • 拿出 f22 运行输出 22。f22 执行完时(函数结束或者遇到 turn),p22 被 resolve,触发 f33 加入微队列。此刻微队列:[ f3, f33]
  • 拿出 f3,运行输出 3。f3 执行完时, p3 变成 fullfilled 状态。
  • 拿出 f33 ,运行输出 33 。f33执行完,p33 变化成 fullfilled 状态。

最终输出结果为 2、22、3、33。以上代码等价于常见链式写法

// code 4
Promise.resolve(1)
  .then(() => console.log(2))
  .then(() => console.log(3))

new Promise(resolve => resolve())
  .then(() => console.log(22))
  .then(() => console.log(33))

到目前为止,你应该真正理解了宏任务、微任务、以及Promise。