宏任務與微任務

本文原創:liuwanhtml

在說微任務與宏任務以前咱們先說一下同步任務與異步任務的概念吧。bash

同步任務與異步任務

JavaScript語言的一大特色就是單線程,也就是說,同一個時間只能作一件事。單線程就意味着,全部任務須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就不得不一直等着。網絡

若是排隊是由於計算量大,CPU忙不過來,倒也算了,可是不少時候CPU是閒着的,由於IO設備(輸入輸出設備)很慢(好比Ajax操做從網絡讀取數據),不得不等着結果出來,再往下執行。異步

JavaScript語言的設計者意識到,這時主線程徹底能夠無論IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回告終果,再回過頭,把掛起的任務繼續執行下去。async

因而,全部任務能夠分紅兩種,一種是同步任務(synchronous),另外一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。 具體來講,異步執行的運行機制以下。(同步執行也是如此,由於它能夠被視爲沒有異步任務的異步執行。)函數

  1. 全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。
  2. 主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,在"任務隊列"之中放置一個事件。
  3. 一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。
  4. 主線程不斷重複上面的3。

以上摘自廖雪峯的博客 JavaScript 運行機制詳解:再談Event Loop.oop

問題

咱們先看一下下面的代碼,而後思考一下輸出的前後順序post

setTimeout(() =>{
    console.log('1')
});

new Promise((resolve) => {
    console.log('2');
    resolve();
}).then(() => {
    console.log('3')
});

console.log('4');
複製代碼

按照同步與異步的概念來看,輸出順序應該是二、四、一、3. 可是,打開控制檯,輸入代碼,查看輸出,順序是這樣的二、四、三、1,發生了什麼? ui

我太難了
想知道發生了什麼就繼續往下看吧。

宏任務與微任務

除了廣義的同步任務和異步任務,咱們對任務有更精細的定義,分爲宏任務和微任務。spa

  1. 宏任務:包括總體代碼script,setTimeout,setInterval;
  2. 微任務:Promise,process.nextTick

注:Promise當即執行,then函數分發到「microtask」隊列,process.nextTick分發到「microtask」隊列 js引擎會把宏任務和微任務放置兩個「任務隊列」中,分別是「macrotask」隊列以及「microtask」隊列。在執行異步任務時,先執行宏任務,而後在執行微任務。

因此如今解釋一下上面問題中的輸出順序問題:

  1. 這段代碼做爲宏任務,進入主線程
  2. 遇到setTimeout,把它的回掉函數放置「macrotask」隊列中,而後接着執行下面的代碼
  3. 遇到Promise,new Promise會當即執行,因而輸出2,其then函數會被放置「microtask」隊列
  4. 遇到console.log('4')直接就執行了
  5. 總體代碼script做爲宏任務已經執行結束,判斷「microtask」隊列中是否有可執行的微任務(then函數),而後執行,輸出3
  6. 至此,整個代碼的第一輪循環結束了,要開始下一輪循環,先去查看「macrotask」隊列,有setTimeout的回掉函數,而後執行,執行結束,輸出1。
  7. 結束。

因此上述問題的輸出順序知道怎麼肥死了吧? 盜一張事件循環,宏任務,微任務的關係圖,以下:

在這裏插入圖片描述
圖說明:進入總體代碼(宏任務)後,開始第一次循環。接着執行全部的微任務。而後再次從宏任務開始,找到符合執行條件的一個宏任務執行完畢,再執行全部的微任務。

複習一下上面的知識點,咱們瞅一眼如下代碼:

console.log('1');                           

setTimeout(() => {                         
    console.log('2');
    process.nextTick(() => {
        console.log('3');
    })
    setTimeout(() => {
        console.log('10')
        new Promise((resolve) => {
            console.log('11');
            resolve();
        }).then(() => {
            console.log('12')
        })
    })    
    new Promise((resolve) => {
        console.log('4');
        resolve();
    }).then(() => {
        console.log('5')
    })
})

process.nextTick(() => {
    console.log('6');
})

new Promise((resolve) => {        
    console.log('7');
    resolve();
}).then(() => {
    console.log('8')
    setTimeout(() => {
        console.log('9')
    })                 
})

console.log('10')
複製代碼

大聲說出答案吧:一、七、十、六、八、二、四、三、五、九、十、十一、12 好吧,咱們分析一下:

  1. 整段代碼做爲宏任務,進入主線程
  2. 遇到console.log('1'),當即執行,並向下執行
  3. 遇到setTimeout,把它的回掉函數fn1,放置「macrotask」隊列中,接着執行下面的代碼
  4. 遇到process.nextTick,把其回調函數fn2放置「microtask」隊列
  5. 遇到Promise,new Promise會當即執行,因而輸出7,其then函數fn3會被放置「microtask」隊列
  6. 遇到console.log('10')直接就執行了
  7. 總體代碼script做爲宏任務已經執行結束,判斷「microtask」隊列中是否有可執行的微任務(fn2以及fn3),隊列具體先進先出的特色,因此先執行fn2,輸出6,而後執行fn3,輸出8,裏面包含setTimeout,把它的回調函數fn4放置「macrotask」隊列中。
  8. 至此,整個代碼的第一輪循環結束了,要開始下一輪循環。如今「macrotask」隊列中有fn1fn4
  9. 先去查看「macrotask」隊列,先執行fn1
  10. 執行fn1,遇到console.log('2'),就輸出,遇到process.nextTick,將其回調函數fn5放置「microtask」隊列,遇到setTimeout,把它的回掉函數fn6放置「macrotask」隊列中,遇到Promise,new Promise會當即執行,因而輸出4,其then函數fn7會被放置「microtask」隊列,即這個宏任務執行完成。「macrotask」隊列裏面有fn4fn6
  11. 如今檢查「microtask」隊列,裏面有fn5fn7,把裏面的任務所有執行完畢, 先執行fn5,輸出3,再執行fn7,輸出5
  12. 至此,又一輪的循環結束了
  13. 再檢查「macrotask」隊列,裏面有fn4fn6,執行fn4,輸出9.
  14. 而如今的「microtask」隊列是空的,再檢查「macrotask」隊列,有fn6
  15. 執行fn6,輸出10,遇到new Promise,輸出11,並把其回調函數fn8放置「microtask」隊列,至此宏任務fn6結束,
  16. 檢查「microtask」隊列,並執行fn8,輸出12,至此,「macrotask」隊列以及「microtask」隊列所有空了。
  17. 結束。

參考文章

相關文章
相關標籤/搜索