在聊js的 宏任务 和 微任务 之前,我们有必要先看看下面这篇阮老师的文章:
JavaScript 运行机制详解:再谈Event Loop
任务队列
Js 中,有两类任务队列:宏任务队列和微任务队列。宏任务队列可以有多个,微任务队列只有一个
- 宏任务:
script(全局任务)、setTimeout、setInterval、setImmediate、I/O、UI rendering - 微任务:
Promise、MutaionObserver、Object.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
代码分析如下:
- console.log(1):同步任务直接执行, 输出1
- setTimeout: 异步宏任务,放入宏任务队列中
- promise实例化过程中的代码是同步的,所以输出3
- promise的then、catch、finally都是异步执行的,所以.then的放入微任务队列中
- console.log(5):同步任务执行执行,输出5,主线程中的同步任务全部执行完毕
- 取出微任务队列任务到主线程中,也就是promise.then, 执行输出4,微任务全部执行完毕
- 取出宏任务队列中的任务,也就是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
代码分析:
- js进入第一个宏任务,输出 1
- 第一个setTimeout放入宏任务中
- 往下到Promise,直接执行promise的同步任务,输出 5, .then微任务放入微任务队列中
- 第二个setTimeout放入宏任务中
- 同步任务,直接执行,输出 11
- 开始执行刚才第3点中的.then微任务,输出 6
- 到目前为止,第一个宏任务执行完成, 输出 1,5,11,6
- 然后我们开始执行第二轮宏任务,也就是第一个setTimeout中的代码
- 同理,输出 2,3,4(按照刚才第一个宏任务的逻辑取理解)
- 至此,输出为: 1,5,11,6,2,3,4
- 我们执行第三轮宏任务,也就是第二个setTimeout中的代码
- 同理,输出为:7,8,10,9
- 至此,我们整段代码共执行了三次事件循环,完整输出为:1,5,11,6,2,3,4,7,8,10,9
我们通过上面两个示例分析,大家对于js的宏任务和微任务都了解了吧。
这样的解析是不是简单易理解。
就问你爽不爽?
await、async的加入
接下来我们如果加入await、async呢,还爽不?
试题都给大家准备好了, 大家可以自己先想想输出是上面, 如下:
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的执行顺序
大家对于 async 和 await 的执行顺序有疑问,也可以参考这篇文章:async和await的介绍传送门