Fork me on GitHub

microtask and macrotask

浏览器的事件循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const setTimeout1 = setTimeout(function () {
console.log('------1-------');
}, 0);
const setTimeout2 = setTimeout(function () {
Promise.resolve(function() {
console.log('---------2-------');
});
console.log('------3-------');
}, 0);
new Promise(function(resolve) {
console.time("Promise start");
for (let i = 0; i < 100000; i++) {
(i === 10000 - 1) && resolve();
}
console.time("Promise end");
}).then(function (res) {
console.log('---------4---------');
});
console.log('---------5---------');

在浏览器中执行以上代码,输出顺序为:

1
2
3
4
5
6
输出promise时间计时
---5---
---4---
---1---
---3---
---2---

废话不多说,由于浏览器的任务队列不止一个,包括microtasks(微任务)和macrotasks(宏任务)。

microtasks有:

  • process.nextTick
  • promise
  • Object.observe
  • MutationObserver

macrotasks有:

  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI渲染

浏览器执行同步和异步任务的整个流程可以浓缩成一句话:

整个的js代码macrotask先执行,同步代码执行完后有microtask执行microtask,没有microtask执行下一个macrotask,如此往复循环至结束

下面的分析都可以带着这句话进行思考。

step1:执行脚本,把这段脚本压入stacks,此时

stacks: [script]
macrotasks: []
microtasks: []

step2:遇到setTimeout1,setTimeout可当作一个task,所以此时

stacks: [script]
macrotasks: [setTimeout1, setTimeout2]
microtasks: []

step3:继续往下执行,new Promise,按同步流程往下走,在i = 99999的时候,resolve()发生,把回调成功的代码段压入microtask queue,
输出new Promise内的执行时间

stacks: [script]
macrotasks: [setTimeout1, setTimeout2]
microtasks: [console.log(‘———4———‘)]

step4:继续往下执行就是console.log(‘—5—‘),此时stacks队列里头的任务结束

stacks: [],
macrotasks: [setTimeout1, setTimeout2]
microtasks: [console.log(‘———4———‘)]

step5:此时stacks里面为空,推出microtasks里的剩余任务,打印4

stacks: [console.log(‘———4———‘)]
macrotasks: [setTimeout1, setTimeout2]
microtasks: []

step6:此时microtasks为空,推出macrotasks里的剩余任务,打印setTimeout1里面的1

stacks: [console.log(‘——1——-‘)]
macrotasks: [setTimeout2]
microtasks: []

step7:继续推出macrotasks里的剩余任务,同时遇到promise,推入到microtasks,打印setTimeout2里面的3

stacks: [console.log(‘——3——-‘)]
macrotasks: []
microtasks: [console.log(‘———2——-‘)]

step8:此时macrotasks里没有任务了,就开始执行microtasks里面的任务,打印2

stacks: [console.log(‘———2——-‘)]
macrotasks: []
microtasks: []

step9:此时microtasks里没有任务了,就开始执行macrotasks里面的任务,macrotasks也没有任务了,执行就结束了。

stacks: []
macrotasks: []
microtasks: []

如此梳理一番,发现以后遇到这种问题,只要按照这个流程走一遍,就没有那么难理解了。

原文

-------------本文结束感谢您的阅读-------------
如果您觉得受益了,欢迎打赏鼓励。