2022了,我们都了解js的宏任务和微任务么

1,184 阅读5分钟

在聊js的 宏任务微任务 之前,我们有必要先看看下面这篇阮老师的文章:

JavaScript 运行机制详解:再谈Event Loop

任务队列

Js 中,有两类任务队列:宏任务队列微任务队列。宏任务队列可以有多个,微任务队列只有一个

  • 宏任务:script(全局任务)、setTimeoutsetIntervalsetImmediateI/OUI rendering
  • 微任务:PromiseMutaionObserverObject.observe(已废弃;Proxy对象替代)、process.nextTick(Node.js)

# 宏任务、微任务是如何执行的?

先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列(Event Queue)中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。

简单点理解:先执行同步和立即执行任务 > 微任务 > 宏任务

示例1

console.log(1)
setTimeout(() => {
    console.log(2)
}, 0)

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

代码分析如下:

  1. console.log(1):同步任务直接执行, 输出1
  2. setTimeout: 异步宏任务,放入宏任务队列中
  3. promise实例化过程中的代码是同步的,所以输出3
  4. promise的then、catch、finally都是异步执行的,所以.then的放入微任务队列中
  5. console.log(5):同步任务执行执行,输出5,主线程中的同步任务全部执行完毕
  6. 取出微任务队列任务到主线程中,也就是promise.then, 执行输出4,微任务全部执行完毕
  7. 取出宏任务队列中的任务,也就是setTimeout,输入2,这时候所有都执行完毕

示例2

大家可以先按照上面说的逻辑自己理解下以下代码:

// 主线程直接执行
console.log(1);

// 放入宏任务队列中,宏任务1
setTimeout(() => {
    console.log(2)
    new Promise(resolve => {
        console.log(3)
        resolve()
    }).then(() => {
        // 微任务2
        console.log(4)
    })
})

// 主线程直接执行
new Promise(resolve => {
    console.log(5)
    resolve()
}).then(() => {
    // 微任务1
    console.log(6)
})

// 放入宏任务队列中,宏任务2
setTimeout(() => {
    console.log(7)
    new Promise(resolve => {
        console.log(8)
        resolve()
    }).then(() => {
        // 微任务3
        console.log(9)
    })
    console.log(10)
})

// 直接执行
console.log(11)

// 1 5 11 6 2 3 4 7 8 10 9

代码分析:

  1. js进入第一个宏任务,输出 1
  2. 第一个setTimeout放入宏任务中
  3. 往下到Promise,直接执行promise的同步任务,输出 5, .then微任务放入微任务队列中
  4. 第二个setTimeout放入宏任务中
  5. 同步任务,直接执行,输出 11
  6. 开始执行刚才第3点中的.then微任务,输出 6
  7. 到目前为止,第一个宏任务执行完成, 输出 1,5,11,6
  8. 然后我们开始执行第二轮宏任务,也就是第一个setTimeout中的代码
  9. 同理,输出 2,3,4(按照刚才第一个宏任务的逻辑取理解)
  10. 至此,输出为: 1,5,11,6,2,3,4
  11. 我们执行第三轮宏任务,也就是第二个setTimeout中的代码
  12. 同理,输出为:7,8,10,9
  13. 至此,我们整段代码共执行了三次事件循环,完整输出为:1,5,11,6,2,3,4,7,8,10,9

我们通过上面两个示例分析,大家对于js的宏任务和微任务都了解了吧。 这样的解析是不是简单易理解

就问你爽不爽?

await、async的加入

接下来我们如果加入awaitasync呢,还爽不?

试题都给大家准备好了, 大家可以自己先想想输出是上面, 如下:

async function async1() {
    console.log(1)
    await async2()
    console.log(2)
}

async function async2() {
    console.log(3)
}

console.log(4)

async1()

setTimeout(function () {
    console.log(5)
}, 0)

new Promise(resolve => {
    console.log(6)
    resolve()
}).then(function () {
    console.log(7)
}).then(function () {
    console.log(8)
})

console.log(9)

按照上面说的套用,我们觉得输出应该是:4,1,3,2,6,9,7,8,5

为什么这样认为呢?我们都知道await是阻塞的,会等await的函数执行完得到结果后,然后继续执行 console.log(2)

直到我后面看到阮老师对于 await 的理解:

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

所以从这段理解来看,async1 函数先执行 console.log(1) ,然后接着往下执行发现是 await 表达式, 并且这个 async2 也是个 async 定义的函数,所以直接执行了 console.log(2), 同时 async2 返回了一个Promise,这时候注意了(此时返回的Promise会被放入到回调队列中等待,await会让出线程,接下来就会跳出 async1函数 继续往下执行。), 然后往下就走到了 setTimeout 这个函数,发现是个宏任务,立马丢到了宏任务队列中,继续往下走到了 new Promise 这里了。

我们看完这段应该就好理解了吧,最终输出是: 4,1,3,6,9,2,7,8,5

结论

至此,我们对于宏任务和微任务介绍的差不多了

async和await的执行顺序

大家对于 asyncawait 的执行顺序有疑问,也可以参考这篇文章:async和await的介绍传送门

调用栈、事件循环、任务队列的介绍

传送门