EventLoop事件循環(huán)中的 MacroTask與 MicroTask

問題來源

在學(xué)習(xí)Promise時在stackoverflow上看到一個解釋Promise運(yùn)行順序回答。
之前在學(xué)習(xí)異步編程中講解了MacroTask和MicroTask, 但在最近深入EventLoop后又有了更多的了解

EventLoop、MacroTask、MicroTask之間的關(guān)系

  • macrotasks 與 microtasks 各自的 API
    macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
    microtasks: process.nextTick, Promises, Object.observe, MutationObserver
  • 一張圖先了解microtasks 與macrotasks 在eventloop隊列里的位置

    這里用了上一章EventLoop 事件循環(huán)文章里的圖,并在回調(diào)隊列里標(biāo)注里microtask的位置。

microtasks 與macrotasks 在eventloop 里的流程

在沒有引入microtasks 概念前事件循環(huán)是這樣執(zhí)行的

while (queue是否有task) {
   執(zhí)行task
}

引入microtasks 概念后

while (queue是否有macrotasks) {
  if (microtasks) 執(zhí)行空microtasks
  再執(zhí)行macrotasks
}

microtasks 與macrotasks 在eventloop 里實際執(zhí)行結(jié)果

// 例1
console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');
// 結(jié)果
script start
script end
promise1
promise2
setTimeout
// 當(dāng)前循環(huán)結(jié)束
// 進(jìn)入下一個循環(huán)

從webapi在Eventloop的執(zhí)行環(huán)境我們可以知道setTimeout在當(dāng)前事件循環(huán)中將會在script end后執(zhí)行,這是沒問題的。
而promise作為microtasks將會在當(dāng)前事件循環(huán)內(nèi)的macrotasks之前執(zhí)行完畢。
setTimeout作為macrotasks在例1中是最后執(zhí)行的。

例2
setImmediate(function(){
    console.log(1);
},0);
setTimeout(function(){
    console.log(2);
},0);
new Promise(function(resolve){
    console.log(3);
    resolve();
    console.log(4);
}).then(function(){
    console.log(5);
});
console.log(6);
process.nextTick(function(){
    console.log(7);
});
console.log(8);
// 執(zhí)行順序
3 4 6 8 7 5 2 1

例2
process.nextTick在node環(huán)境中,屬于microtask
setImmediate在macrotasks,優(yōu)先級小于setTimeout
定義new Promise() 是同步代碼,在棧內(nèi)先執(zhí)行

例3
const p = new Promise((res, rej) => {
  res(1)
  console.log('定義new Promise - 同步')
}).then(val => {
  console.log('microtask start')
  console.log('執(zhí)行then,enqueue micarotask 1')
  console.log(val) // 1
})

Promise.resolve({
  then(res, rej) {
    console.log('執(zhí)行then,enqueue micarotask 2')
    res(5)
  }
}).then(val => {
  console.log('執(zhí)行then,enqueue micarotask 3')
  console.log(val) // 5
})

console.log('逐行執(zhí)行1 - 同步')
console.log('逐行執(zhí)行2 - 同步')
console.log(3) // 3

setTimeout(console.log, 0, 'macrotask start') // 4 
setTimeout(console.log, 0, 4) // 4
定義new Promise - 同步
逐行執(zhí)行1 - 同步
逐行執(zhí)行2 - 同步
3
// 同步隊列執(zhí)行完畢為空 進(jìn)入下一個棧

microtask start
執(zhí)行then,enqueue micarotask 1
1
執(zhí)行then,enqueue micarotask 2
執(zhí)行then,enqueue micarotask 3
5
// microtask執(zhí)行完畢為空 進(jìn)入下一個棧

macrotask start
4
// macrotask執(zhí)行完畢為空 結(jié)束

例3
定義new Promise是同步函數(shù)
Promise.resolve等api為異步micarotask

例4
<div class="outer">
  <div class="inner"></div>
</div>

var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
// 同時點(diǎn)擊到兩個div時執(zhí)行結(jié)果
click
promise
mutate
click
promise
mutate
timeout
timeout

例4執(zhí)行效果
沒點(diǎn)擊前:
1綁定new MutationObserver 存入瀏覽器資源
2綁定兩個div元素的click事件 存入瀏覽器資源

3觸發(fā)outer元素click的onClick 存入瀏覽器資源
4觸發(fā)inner元素click的onClick 存入瀏覽器資源
5先執(zhí)行outer的回調(diào)
6輸出click
7執(zhí)行setTimeout - macrotask存入瀏覽器資源
8執(zhí)行outer.setAttribute('data-random', Math.random()),觸發(fā)MutationObserver - marcotask 等待microtask先執(zhí)行
9執(zhí)行Promise.resolve - microtask 輸出promise
10microtask 執(zhí)行完畢,執(zhí)行MutationObserver輸出mutate
-----下面執(zhí)行的并不是outer回調(diào)里的setTimeout------
11執(zhí)行inner的回調(diào)
12輸出inner回調(diào)的click
13執(zhí)行inner回調(diào)的setTimeout - macrotask存入瀏覽器資源
14執(zhí)行inner回調(diào)outer.setAttribute('data-random', Math.random()),觸發(fā)MutationObserver - marcotask 等待microtask先執(zhí)行
15執(zhí)行inner回調(diào)的Promise.resolve - microtask 輸出promise
16microtask 執(zhí)行完畢,執(zhí)行MutationObserver輸出mutate
--最后因為兩個setTimeout都是在觸發(fā)inner回調(diào)后存入瀏覽器資源的--
所以最后兩個setTimeout回調(diào)完成排入隊列執(zhí)行.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容