Javascript中的Microtask和Macrotask——從一道不多有人能答對的題目提及

首先咱們來看一道題目,以下javascript代碼,執行後會在控制檯打印出什麼內容?javascript

 1 async function async1() {
 2   console.log('async1 start');
 3   await async2();
 4   console.log('async1 end');
 5 }
 6 
 7 async function async2() {
 8   console.log('async2 start');
 9   return new Promise((resolve, reject) => {
10     resolve();
11     console.log('async2 promise');
12   })
13 }
14 
15 console.log('script start');
16 setTimeout(function() {
17   console.log('setTimeout');
18 }, 0);  
19 
20 async1();
21 
22 new Promise(function(resolve) {
23   console.log('promise1');
24   resolve();
25 }).then(function() {
26   console.log('promise2');
27 }).then(function() {
28   console.log('promise3');
29 });
30 console.log('script end');

說實話,真正能在面試中把這道題目答對的前端工程師百裏挑一。咱們先來瞧一下答案吧。把以上代碼存到test.js文件中,並用node執行一下,結果以下:html

若是把以上代碼貼到一個網頁中的script標籤裏面,而後打開這個網頁,再打開控制檯,能夠看到以下輸出(Chrome 64位 63.0.3239.84):前端

結果和node打印的如出一轍。那麼爲何是這個順序呢?html5

 

咱們都知道js的單線程特性(html5的web worker不算在內~)以及良好的異步支持。在單線程的前提下,異步任務到底何時開始執行,實際上是有兩個隊列來進行管理,即Macrotask和Microtask(只有一個字母的差距,不要認錯……)。在當前正在執行的線程中,若是碰到屬於Macrotask的異步任務,則放入Macrotask隊列;碰到Microtask的異步任務則放入Microtask隊列。注意這裏只是把任務放入隊列,並不會執行它。等到當前主線程任務執行完畢以後,會依次從Microtask隊列中取出任務執行,在執行期間固然仍是遵循碰到異步任務放入相應隊列的原則。等到Microtask任務所有執行過了,此時再從Macrotask隊列中取出一個任務執行。java

 

屬於Macrotask的任務有:node

setTimeout,setInteveral,script標籤,I/O,UI渲染

屬於Microtask的任務有:web

Promise,async/await,process.nextTick,Object.observe,MutationObserver

(事實上,即便一樣是Microtask,內部也是有優先級的差異的,例如NodeJS的實現上,process.nextTick比Promise要先執行。相關問題能夠瞧瞧這個鏈接:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 。反正我瞧到一半就放棄了,好在async/await和Promise沒有優先級差異)面試

 

而後咱們來分析一下本題中的執行順序:promise

【1】第15行執行,打印出script start前端工程師

【2】第16至18行,把回調任務放入Macrotask (目前Macrotask:第16行setTimeout,Microtask:空)

【3】第20行,執行async1函數,先打印出第2行的async1 start

【4】第3行的async2先執行,打印出第8行的async2 start

【5】第9行至第12行遇到Promise,先打印出第11行的async2 promise(注意無論你resolve寫在new Promise的函數什麼位置,都跟寫到最後一句同樣!)

【6】第3行的async2返回了Promise,而且async2前面有await修飾,所以後面第4行的任務被放到Microtask(目前Macrotask:第16行setTimeout,Microtask:第4行)

【7】第22至25行,打印出promise1,並把第26行放入Microtask,注意第28行還沒執行到,因此這行什麼都不作(目前Macrotask:第16行setTimeout,Microtask:第4行,第26行)

【8】第30行打印script end(目前Macrotask:第16行setTimeout,Microtask:第4行,第26行)

【9】腳本主線程執行結束,如今拿出來一個Microtask,即第4行,打印async1 end(目前Macrotask:第16行setTimeout,Microtask:第26行)

【10】再拿出來一個Microtask,即第26行,打印promise2,此時因爲第26行後面跟着then,因此把第28行插入Microtask(目前Macrotask:第16行setTimeout,Microtask:第28行)

【11】再拿出來一個Microtask,即第28行,打印promise3(目前Macrotask:第16行的setTimeout,Microtask:空)

【12】Microtask沒有了,執行下一個Macrotask,即第16行的setTimeout,打印setTimeout,結束

 

須要注意的是,如下兩種寫法,效果是如出一轍的(resolve的位置無所謂):

寫法1:
new Promise((resolve, reject) => {
  console.log('1111');
  resolve();
  console.log('2222');
});

寫法2:
new Promise((resolve, reject) => {
  console.log('1111');
  console.log('2222');
  resolve();
});

 

另外,對於Promise的鏈式調用,如new Promise(....).then(...).then(...)....,一次只放第一個then的內容進入Microtask,等第一個then執行的時候,會把第二個then放入Microtask,而不是一次把兩個then都放進去。

相關文章
相關標籤/搜索