微任务宏任务执行顺序(async/await,promise then中需要注意的地方)

1,478 阅读5分钟

最近做了一道程序输出题,发现以前做过然后现在又忘记了,就想记录下来,希望能更加地浅显易懂地表达它是如何得出它的结果的。

我们先来看一段代码,结果会输出什么?

1   async function async1() {
2       console.log('async1 start')
3       await async2()
4       console.log('async1 end')         // 1
5   }
6    
7   async function async2() {
8       console.log('async2')
9   }
10    
11  console.log('script start')
12    
13  async1()
14    
15  setTimeout(() => {                    // 2
16      console.log('setTimeout')
17  }, 0)
18    
19  new Promise((resolve) => {
20      console.log('promise')
21      resolve()
23  }).then(function () {                 // 3
24      console.log('promise1 start')
25      return 'promise1 end'
26  }).then(res => {                      // 4
27      console.log(res)
28  }).then(res => {                      // 5
29      console.log(res)
30  })
31    
32  console.log('script end')

题中有async/await、promise、setTimeout,很明显是想要考微任务、宏任务的执行顺序。 我们从上往下看,先是两个async函数的声明

async是ES7才有的与异步操作有关的关键字,async函数返回一个Promise对象,可以使用then方法添加回调函数,即
async fn1() {}
console.log(fn1()) // 打印 一个Promise对象

然后,11行打印 script start

13行执行函数async1,打印 async1 start ; 遇到await操作符

await操作符用于等待一个Promise对象,它只能在异步函数async function内部函数使用
[return_value] = await expression // expression---一个Promise对象 或 任何需要等待的值
需要注意的是,await语句之后的代码,相当于Promise的callback,是微任务 即

await async2()

相当于

await new Promise(resolve => {
    console.log('async2')
    resolve()
}).then(() => { // 微任务1
    console.log('async1 end')
})

async标记的函数返回一个Promise对象,因此,我们又可以看成

await async2().then(() => { // 微任务1
    console.log('async1 end')
})

因此,题中遇到await操作符,执行async2函数,打印 async2 ,之后的代码作为一个微任务加入到微任务队列中,等待同步代码, 即调用栈中任务执行完毕后执行。
此时,微任务队列为[1] (为题中右边标序)

继续向后走,遇到setTimeout为宏任务,加入到宏任务队列中
此时,宏任务队列为[2]

遇到new Promise直接执行,打印 promise , 遇到then,将回调函数放入微任务队列中,微任务队列为[1, 3]
继续遇到then,加入到微任务队列中,微任务队列为[1, 3, 4],又遇到then,继续加入微任务队列,此时,微任务队列为[1, 3, 4, 5]

这里删除了之前的说法,是因为在后面的变形中,我理解到这样的说法是错误的,then加入微任务队列的时机,并不是像我们臆想的那样顺序连续地加入,而是

补充:
(1)第一个then回调是在Promise对象的resolve后加入微任务队列的
(2)后续的then回调都依赖于上一个then回调代码的执行结束,同时关于前一个then的同步或异步,其他博主也有讲到:

①上一个then回调代码都是同步执行,执行结束后下一个then可以加入到微任务队列中;
②上一个then回调有return关键字时,需要等待return的内容完全执行完毕后,下一个then才能加入到微任务队列中

因此解题思路应为:resolve后遇到第一个then,我们将其放入微任务队列中,微任务队列为[1, 3]

继续执行程序,打印 script end ,此时程序中的同步代码已经执行完毕,调用栈为空。

开始将微任务队列的任务逐个加入调用栈中执行,当前微任务队列为[1, 3],因此先执行标识1处的微任务,打印 async1 end微任务队列变为[3]

执行标识3处微任务,打印 promise1 start微任务队列变为空

至此,第一个then执行完毕,第二个then加入到微任务队列中,为[4],执行标识4处微任务,打印promise1 end,接着第三个then加入到微任务队列中,为[5],打印undefined

第二个then因为有上一个then通过return传递的'promise1 end',而第二个then没有传递参数给予到下一个then,因此第三个then打印的是undefined。

此时微任务队列为空,开始执行宏任务队列中的任务,只有一个标识2处的宏任务,打印 setTimeout

至此,程序执行完毕。 最后结果为

image.png

变形

    async function async1() {
        console.log('async1 start')
        await async2()
        console.log('async1 end')           // 1
        new Promise(resolve => {
            console.log('async promise')
            resolve('async promise start')
        }).then(res => {                    // 2
            console.log(res)
        })
    }

    async function async2() {
        console.log('async2')
    }
    
    async1()

    new Promise((resolve) => {
        console.log('promise')
        resolve()
    }).then(function () {                 // 3
        console.log('promise1 start')
        return 'promise1 end'
    }).then(res => {                      // 4
        console.log(res)
    }).then(res => {                      // 5
        console.log(res)
    })

我们将代码聚焦到重要的部分,并在原来的基础上进行扩展,此时程序又会输出什么呢?我们先来看一下输出的结果

image.png 前五个输出我们可以通过上一个例子同理得出,从第六个开始输出顺序就需要第一个例子中对于then进入微任务的时机的补充来说明原因了。

async1函数中,await后的代码是微任务,我们可以将

await async2()
console.log('async1 end')
new Promise(resolve => {
    console.log('async promise')
    resolve('async promise start')
}).then(res => {
    console.log(res)
})

看成

await new Promise(resolve1 => {
    console.log('async2')
    resolve1()
}).then(() => {                          // 1
    console.log('async1 end')
    new Promise(resolve => {
        console.log('async promise')
        resolve('async promise start')
    }).then(res => {                     // 2
        console.log(res)
    })
})

await async2()执行后,输出async2,await后的代码被进入微任务队列中,微任务队列[1],接着执行同步代码,输出promise,与此同时,标识3处微任务进入队列,微任务队列[1,3]

同步代码执行完毕,调用栈为空,微任务队列逐个进入调用栈中执行,执行微任务1,执行标识1处then中回调函数,顺序输出async1 end async promise,标识2处微任务进入队列,微任务队列[3,2]

执行微任务3,输出promise1 start,微任务4进入队列,微任务队列[2,4]。接着执行微任务2,以此类推,依次输出async promise start promise1 end undefined

至此,对于微任务宏任务,async/await,promise/then的解说就到此结束啦,欢迎探讨!