JavaScript 宏观和微观任务

前言

定义:宿主机(浏览器)发起的任务称为宏观任务,JavaScript 引擎发起的任务称为微观任务。

在宏观任务中,JavaScript 的 Promise 还会产生异步代码,JavaScript 必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列。

Promise 永远在队列尾部添加微观任务。setTimeout 等宿主 API,则会添加宏观任务。

代码验证

先来看下面 Promise 与 setTimeout 混用的这段代码,通过 setTimeout 执行 console.log(‘d’),通过 Promise 延迟5秒执行 console.log(‘c’)。

第 9 行 while 循环 形成一个自旋锁 确保5秒之后执行 console.log(‘c’)

1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(() => console.log('d'), 0)
const r = new Promise((resolve) => {
console.log('a')
resolve()
})
r.then(() => {
// 为了从感官上确保 c 是在 d 之后被打印的,这里延迟 5 秒打印 c
const begin = Date.now()
while (Date.now() - begin < 5000);
console.log('c')
})
console.log('b')

从语义上感觉 c 应该是最后被打印的。但实际打印顺序是a b c d,无论 setTimeout 是放在顶部还是底部都不会改变打印顺序。

分析

因为 Promise 产生的是 JavaScript 引擎内部的微任务,而 setTimeout 是浏览器 API,它产生宏任务。setTimeout 把整段代码分隔成 2 个宏任务。

2-12 行为第 1 个宏任务(优先执行),setTimeout 中的 console.log(‘d’) 为第 2 个宏任务,在第 1 个宏任务执行完之后再执行。

所以这也很好的解释了为何 d 总是最后被打印。