promise與settime的執行順序

setTimeout(() => {
    console.log('timeout')
})
new Promise((resolve) => {
    console.log('before')
    resovle('then')
    console.log('after')
}).then(res => {
    console.log(res)
})

這段代碼的執行結果爲 before -> after -> then -> timeout 。

不要懷疑,這是就是正確答案。

此時你是不是滿腦袋的問號呢,如果是,那就請客官您繼續往下看,看完本文,您就會覺得原來這段代碼只不過是 a piece of cake。

重點知識來啦:

什麼是宏任務(Macrotasks)和微任務(Microtasks)

首先我們應該知道JavaScript 是單線程的腳本語言(在執行某一段代碼的時候是不會同時執行另一段代碼的)。

如果所有的代碼都同步執行的話會有什麼結果呢,比如當程序向後臺發送了一個請求,此時你能做的只有雙手合十祈禱網速快一點,因爲程序只有在得到後臺迴應的時候纔會執行其他的操作。

於是就出現了異步事件的概念,當程序向後臺發送一個請求以後,可以做一些其他的事,即使請求返回的數據程序已經拿到了,也需要等到程序做完正在做的事,纔會處理請求返回的數據,這就是異步事件

而異步任務又分爲宏任務和微任務,其中微任務的優先級高於宏任務。

宏任務有: setTimeout, setInterval, setImmediate(node中),requestAnimationFrame(瀏覽器);

微任務有:process.nextTick(node中), MutationObserver(瀏覽器),catch, finally, Promise.then。

其中需要注意的是Promise中的函數遇到立即執行,只有promise所對應的then中執行的代碼片段需要加入‘微任務’隊列(隊列遵循先進先出原則)。

總結經驗就是:全局的js語句與promise中的語句順序執行(遇到就執行),而宏任務遇到需放入宏任務隊列,遇到微任務則放入微任務隊列,當前代碼溜了一遍之後(之所以說溜了一遍是因爲並不是嚴格意義上的代碼執行完畢了),再將微任務中的代碼塊按順序執行,當執行完隊列中的所有微任務,就進入宏任務隊列依次執行宏任務代碼塊

對於喜歡看圖說話的人,那我就把網上隨處可見的圖搬過來送你們啦(不用謝^_^)。

既然有了理論的概念,下面就通過例子來檢測你有沒有掌握(就以最常見的Promise和setTimeout來做實例)

目前你正處於小白階段,趕快通過磨練來晉級吧。

實例驗證

1. 單個的宏任務或微任務

console.log('start');
setTimeout(() => {
    console.log('setTimeout');
}, 0);
console.log('end');

輸出答案爲:start -> end -> setTimeout

解析: 普通js代碼遇到就直接執行,宏任務需先放入宏任務隊列,到當前環境代碼溜一遍之後發現微任務隊列裏面沒有需要執行的代碼塊,則執行宏任務。

console.log('start');
new Promise(resolve => {
    console.log('promise');
    resolve('then');
}).then((data) => {
    console.log(data);
})
console.log('end');

輸出答案爲:start -> promise -> end -> then

解析:普通js代碼遇到就直接執行,微任務需先放入微任務隊列,到當前環境代碼溜一遍之後執行微任務中的代碼,然後發現宏任務隊列裏面沒有代碼塊需要執行,則整個代碼執行完畢。

         如果你上面的兩個題都做對了,那恭喜你已經將理論與實踐結合起來了,但是想要進一步理解,需要通過下一個題目來檢驗。

        當然如果你沒有回答正確請不要灰心,可以喝口水緩緩,然後從頭再梳理一遍。

2. 宏任務和微任務混搭:

console.log('start');
new Promise((resolve) => {
    console.log('promise1')
    resolve('then1')
    console.log('promise1 end')
}).then(res => {
    console.log(res)
})
console.log('middle');
setTimeout(() => {
    console.log('timeout1')
})
new Promise((resolve) => {
    console.log('promise2')
    resolve('then2')
    console.log('promise2 end')
}).then(res => {
    console.log(res)
});
console.log('end');

輸出答案爲:start -> promise1 -> promise1 end -> middle -> promise2 -> promise2 end -> end -> then1 -> then2 -> timeout1。

解析:先輸出start,然後輸出promise1,再輸出promise1 end 我就得你應該已經明白爲什麼了,那我們接下來從這裏開始分析,此時應該在微任務隊列中放入 then第一個promise對應的then語句;遇到middle,直接執行,timeout 放入宏任務隊列;遇到第二個promise,直接執行promise中的語句,然後將對應的then中的代碼塊放入微任務隊列,成爲微任務2號;在執行end。到目前爲止,所有的代碼都溜了一遍了,下面先依次執行微任務列表中的代碼塊,爲then1,then2,最後執行宏任務timeout1。

怎麼樣,你有沒有get到關鍵所在呢,接下來就要放大招了,你準備好了嗎?

3.每個promise對應多個then方法

console.log('start');
setTimeout(() => {
    console.log('timeout1')
}, 0);
new Promise((resolve, reject) => {
    console.log('promise1 start');
    resolve('promise1 end');
}).then((data) => {
    console.log(data, 1);
    return new Promise(resolve => {
        console.log('promise3');
        resolve(data);
    })
}).then(data => {
    console.log(data, 2)
});
new Promise((resolve, reject) => {
    console.log('promise2 start');
    resolve('promise2 end');
}).then((data) => {
    console.log(data, 1);
    return new Promise(resolve => {
        console.log('promise4');
        resolve(data);
    })
}).then(data => {
    console.log(data, 2)
});
console.log('finish');

輸出答案:start -> promise1 start -> promise2 start -> finish -> promise1 end 1 -> promise3 -> promise2 end 1 -> promise4 -> promise1 end 2 -> promise2 end 2 -> timeout1

解析:具體運行步驟如下:

 ① 執行一遍全局代碼:

    start: 輸出start -> timeout1: 將timeout1放入宏任務隊列 -> promise1: 輸出promise1 start -> then1.1: 將then1.1放入微任務隊列 -> promise2: 輸出promise2 start -> then2.1: 將then2.1放入微任務隊列 -> finish:輸出finish
    執行完畢時,各隊列及輸出如下:

        宏任務隊列:[ 'timeout1' ]
        微任務隊列: [ 'then1.1', 'then2.1' ]
        輸出:start -> promise1 start -> promise2 start -> finish

執行微任務隊列中的第一位:

    執行then1.1:輸出 promise1 end 1 -> promise3: 輸出promise3-> then1.2: 將then1.2 放入微任務隊列
    執行完畢時,各隊列及輸出如下:

        宏任務隊列:[ 'timeout1' ]
        微任務隊列: [ 'then2.1', 'then1.2' ]
        輸出:start -> promise1 start -> promise2 start -> finish -> promise1 end 1 -> promise3

③ 執行微任務隊列中的第一位:

    執行then2.1:輸出 promise2 end 1 -> promise4: 輸出promise4 -> then2.2: 將then2.2 放入微任務隊列
    執行完畢時,各隊列及輸出如下:
        宏任務隊列:[ 'timeout1' ]
        微任務隊列: [ 'then1.2', 'then2.2' ]
        輸出:start -> promise1 start -> promise2 start -> finish -> promise1 end 1 -> promise3 -> promise2 end 1 -> promise4

 執行微任務隊列中的第一位:

    執行then1.2:直接輸出 promise1 end 2
    執行完畢時,各隊列及輸出如下:
        宏任務隊列:[ 'timeout1' ]
        微任務隊列: [ 'then2.2' ]
        輸出:start -> promise1 start -> promise2 start -> finish -> promise1 end 1 -> promise3 -> promise2 end 1 -> promise4 -> promise1 end 2

 執行微任務隊列中的第一位:

    執行then2.2:直接輸出 promise2 end 2
    執行完畢時,各隊列及輸出如下:
        宏任務隊列:[ 'timeout1' ]
        微任務隊列: []
        輸出:start -> promise1 start -> promise2 start -> finish -> promise1 end 1 -> promise3 -> promise2 end 1 -> promise4 -> promise1 end 2 -> promise2 end 2

 檢查微任務隊列:

    此時微任務隊列爲空,則進行下一步

⑦ 執行宏任務隊列中的第一位:

    執行timeout1: 直接輸出timeout1
    執行完畢時,各隊列及輸出如下:
        宏任務隊列:[]
        微任務隊列: []
        輸出:start -> promise1 start -> promise2 start -> finish -> promise1 end 1 -> promise3 -> promise2 end 1 -> promise4 -> promise1 end 2 -> promise2 end 2 -> timeout1

此時,整段代碼執行完畢,最終輸出結果如下:

        start -> promise1 start -> promise2 start -> finish -> promise1 end 1 -> promise3 -> promise2 end 1 -> promise4 -> promise1 end 2 -> promise2 end 2 -> timeout1

 

答對了不要高興得太早呀,終極大挑戰等着你呢!come on 

4. 宏任務與微任務嵌套

// 我爲所有的語句標了代號,以便後面解析使用
// timeout1
setTimeout(() => console.log('timeout1'), 0);
// timeout2
setTimeout(() => {
    console.log('timeout2');
    // promise1
    new Promise(resolve => {
        console.log('promise1');
        resolve();
        // then1
    }).then(() => {
        console.log('then1');
        // promise2
        new Promise(resolve => {
            console.log('promise2');
            resolve();
            // then2
        }).then(() => {
            console.log('then2');
        })
        // here
        console.log('here');
    })
    // timeout3
    setTimeout(() => console.log('timeout3'), 0);
}, 0);
// timeout4
setTimeout(() => console.log('timeout4'), 0);
// promise3
new Promise(resolve => {
    console.log('promise3');
    resolve();
    // then3
}).then(() => {
    console.log('then3');
})

輸出答案爲:promise3 -> then3 -> timeout1 -> timeout2 -> promise1 -> then1 -> promise2 -> here -> then2 -> timeout4 -> timeout3

解析:具體執行步驟如下:

 ① 執行一遍整體代碼:

    執行過程:

    timeout1: 入宏任務隊列 -> timeout2: 入宏任務隊列 -> timeout4入宏任務隊列 -> promise3:輸出‘promise3’ -> then3: 入微任務隊列。

    此時各隊列和輸出如下:
        宏任務隊列:[ 'timeout1', 'timeout2', 'timeout4' ]
        微任務隊列:['then3']

        輸出:promise3

 執行當前微任務隊列:

    then3:直接輸出then3

    執行完畢時:

        宏任務隊列:[ 'timeout1', 'timeout2', 'timeout4' ]

        微任務隊列:[]

        輸出:promise3 -> then3

 執行宏任務隊列排在第一位的代碼塊:

    宏任務隊列中排在第一位的爲timeout1,比較簡單,直接輸出timeout1就好。

    執行完畢時:

        宏任務隊列:[ 'timeout2', 'timeout4' ]

        微任務隊列:[]

        輸出:promise3 -> then3 -> timeout1

④ 檢查當前微任務隊列:

    當前微任務隊列爲空,則繼續執行宏任務隊列中第一位的代碼塊。

 執行宏任務隊列排在第一位的代碼塊(其實就是重複上面的步驟,但是過程中需要謹慎)

     執行一遍宏任務隊列中排在第一位的代碼塊timeout2,過程如下:
        輸出timeout2 -> promise1: 輸出promise1-> then1: 放入微任務隊列 -> timeout3:放入宏任務隊列

    此時:

        宏任務隊列:['timeout4', 'timeout3']

        微任務隊列:[ 'then1' ]

        輸出:promise3 -> then3 -> timeout1 -> timeout2 -> promise1

⑥ 執行當前微任務隊列中的代碼塊

    執行then1過程如下:
        輸出then1-> promise2:輸出promise2 -> then2: 放入微任務隊列 -> here: 輸出here

    執行完畢時:

        宏任務隊列:[ 'timeout4', 'timeout3']
        微任務隊列:['then2']
        輸出:promise3 -> then3 -> timeout1 -> timeout2 -> promise1 -> then1 -> promise2 -> here

 執行微任務隊列的代碼塊

    執行then2: 直接輸出then2

    此時:

        宏任務隊列:['timeout4', 'timeout3']
        微任務隊列:[]
        輸出:promise3 -> then3 -> timeout1 -> timeout2 -> promise1 -> then1 -> promise2 -> here -> then2

 檢查微任務隊列

    此時微任務隊列中沒有需要執行的代碼塊,進行下一步,執行宏任務隊列中的第一位

 執行宏任務隊列中排在第一位的代碼塊

    執行timeout4:直接輸出timeout4

    此時:

        宏任務隊列:['timeout3']
        微任務隊列:[]
        輸出:promise3 -> then3 -> timeout1 -> timeout2 -> promise1 -> then1 -> promise2 -> here -> then2 -> timeout4

⑩ 檢查微任務隊列

    此時微任務隊列中沒有需要執行的代碼塊,進行下一步,執行宏任務隊列中的第一位

 執行宏任務隊列中排在第一位的代碼塊

    執行timeout3:直接輸出timeout3

    任務隊列中排在第一位的代碼塊

 

    執行timeout3:直接輸出timeout3

    此時:

        宏任務隊列: []

        微任務隊列:[]

        輸出:promise3 -> then3 -> timeout1 -> timeout2 -> promise1 -> then1 -> promise2 -> here -> then2 -> timeout4 -> timeout3

至此,整個程序完執行完畢,因此最終輸出爲:

promise3 -> then3 -> timeout1 -> timeout2 -> promise1 -> then1 -> promise2 -> here -> then2 -> timeout4 -> timeout3

上面的解析有點詳細,但是對於做錯的人看起來會比較方便一點。

如果你上面的都回答正確,那麼恭喜你,可以去迎接工作中過關斬將了。

 

    

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