異步執行順序——宏任務與微任務不同環境下的出隊規則

導讀

javascript是一門單線程語言,一切javascript版的多線程都是用單線程模擬出來的,所以代碼執行還是順序執行的原則,只不過編寫的順序被執行環境重新“編排”了一下而已。

執行過程中,我們把任務分爲同步任務和異步任務,而異步任務又分爲宏任務,微任務。
一般順序:同步->微任務->宏任務

  • macro-task(宏任務):包括整體代碼script,setTimeout,setInterval
  • micro-task(微任務):Promise,process.nextTick

node環境與瀏覽器環境區別:(這裏先寫出區別,下面做詳細介紹,具體不同環境爲什麼異步輸出不同。)

node環境中,任務全部出隊,並執行。

瀏覽器環境,出隊一個任務,並執行。

任務入隊規則

異步任務隊列分爲宏任務隊列,微任務隊列。

代碼順序執行,碰到異步代碼後放入對應隊列內。第一遍代碼執行完畢後,會取出微任務隊列內的代碼繼續執行,執行完後再取出宏任務隊列內的代碼繼續執行。以此反覆執行直到最後執行完所有任務。

運用遞歸思想描述

function 代碼執行(任務){
	任務執行,異步的代碼入隊
	代碼執行(微任務出隊)
	代碼執行(宏任務出隊)
}

這裏說明一下:由於node環境下的事件監聽依賴libuv與前端環境不完全相同,輸出會有差異。

主要原因:雖然規則相同,但是入隊的順序會有差異,出隊任務數量也有不同,所以輸出會有差異

實例演示

下面結合實例,將在node端和瀏覽器端分別說明其差異性。

使用代碼:

    console.log('1');
    setTimeout(function() { //timeout1
        console.log('2');
        new Promise(function(resolve) {
            console.log('4');
            setTimeout(function(){//timeout2
                console.log('5');
                resolve()//promise1
            })
        }).then(function() {
            console.log('6');
        })
    })
    new Promise(function(resolve) {
        console.log('8');
        resolve();//promise2
    }).then(function() {
        console.log('9');
    })
    setTimeout(function() { //timeout3
        console.log('10');

        new Promise(function(resolve) {
            console.log('12');
            resolve();//promise3
        }).then(function() {
            console.log('13');
        })
        setTimeout(function(){//timeout4
            console.log('14')
            new Promise(function(resolve){
                console.log('15')
                resolve() //promise4
            }).then(function() {
                console.log('16');
            })
        })
    })
瀏覽器端:

這裏建議打開兩個頁面,一邊對照代碼一邊看一下步驟。

第一次執行代碼:輸出1,timeout1壓入宏隊列,輸出8,promise2壓入微隊列,timeout3壓入宏任務隊列。

//在瀏覽器端,邊執行代碼,邊把異步代碼加入對應隊列,這一次執行都是同步代碼。

//宏任務隊列內[timeout1,timeout3],微任務隊列[promise2]

第二次執行代碼(promise2出隊):輸出9

//微任務隊列優先級高,先執行微任務,瀏覽器端只有一個任務出隊並執行。

//宏任務隊列內[timeout1,timeout3],微任務隊列[]

第三次執行代碼(timeout1出隊):輸出2,輸出4,timeout2壓入宏隊。

//宏任務隊列內[timeout3,timeout2],微任務隊列[]

第四次執行代碼(timeout3出隊):輸出10,輸出12,promise3壓入微隊列,timeout4壓入宏隊列。

//宏任務隊列內[timeout2,timeout4],微任務隊列[promise3]

第五次執行代碼(promise3出隊):輸出13。

//宏任務隊列內[timeout2,timeout4],微任務隊列[]

第六次執行代碼(timeout2出隊):輸出5,promise1壓入微隊列。

//宏任務隊列內[timeout4],微任務隊列[promise1]

第七次執行代碼(promise1出隊):輸出6。

//宏任務隊列內[timeout4],微任務隊列[]

第八次執行代碼(timeout4出隊):輸出14,輸出15,promise4壓入微隊列。

//宏任務隊列內[],微任務隊列[promise4]

第九次執行代碼(promise4出隊):輸出16。

//宏任務隊列內[],微任務隊列[]

順序爲:1,8,9,2,4,10,12,13,5,6,14,15,16

node端

第一次執行代碼:輸出1,timeout1壓入宏隊列,輸出8,promise2壓入微隊列,timeout3壓入宏任務隊列。

//第一次執行node端和瀏覽器端二者相同。

//宏任務隊列內[timeout1,timeout3],微任務隊列[promise2]

第二次執行代碼(promise2出隊):輸出9

//二者相同。

//宏任務隊列內[timeout1,timeout3],微任務隊列[]

第三次執行代碼([timeout1,timeout3出隊):輸出2,輸出4,timeout2壓入宏隊列,輸出10,輸出12,promise3壓入微隊列,timeout4壓入宏隊列。

//這裏我們可以看到,node與瀏覽器不同之處,timeout1,timeout3一起出隊,所以內部的同等級(同步代碼)就一起執行了。node環境的這一次執行完成了瀏覽器多次執行步驟。

//宏任務隊列內[timeout2,timeout4],微任務隊列[promise3]

第四次執行代碼(promise3出隊):輸出13。

//宏任務隊列內[timeout2,timeout4],微任務隊列[]

第五次執行代碼(timeout2,timeout4出隊):輸出5,promise1壓入微隊列,輸出14,輸出15,promise4壓入微隊列。

//在這裏輸出數值與瀏覽器端開始有差異。

//宏任務隊列內[],微任務隊列[promise1,promise4]

第六次執行代碼(promise1,promise4出隊):輸出6,輸出16。

//宏任務隊列內[],微任務隊列[]

順序爲:1,8,9,2,4,10,12,13,5,14,15,6,16

總結:

簡單結構的代碼,一般執行順序:同步->微任務->宏任務。複雜結構代碼,微任務優先級大於宏任務優先級。同步代碼邊執行,邊把異步任務入隊,然後系統選擇出隊的任務再一次新一輪執行。

node環境與瀏覽器環境區別:node環境中,任務全部出隊,並執行。瀏覽器環境,出隊一個任務,並執行。

備註1

console.log("11")
new Promise(function(resolve) {
        console.log('12');//這裏代碼屬於同步代碼,只有then內代碼纔是異步代碼,同上面“console.log("11")”代碼段一起順序執行。
        resolve();
    }).then(function() {
        console.log('13');
    })

備註2

在node環境中process.nextTick()比Promise優先級高,所以優先執行

new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
process.nextTick(function() {
    console.log('6');
})
//7,6,8

備註3

這一次,徹底弄懂 JavaScript 執行機制:https://juejin.im/post/59e85eebf265da430d571f89

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章