前端碎碎念 之 nextTick, setTimeout 以及 setImmediate 三者的執行順序

『前端碎碎念』系列會記錄我平時看書或者看文章遇到的問題,通常都是比較基礎可是容易遺忘的知識點,你也可能會在面試中碰到。 我會查閱一些資料並可能加上本身的理解,來記錄這些問題。更多文章請前往個人我的博客javascript

這個問題是有關執行順序和Event Loop的。關於Event Loop和任務隊列等概念,能夠先閱讀我引用中的文章,本文主要分析一些存在的疑惑點。前端

下面這個例子比較典型:java

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);

//輸出結果是3 4 6 8 7 5 2 1

在解釋輸出結果以前,咱們來看幾個概念:node

macro-task: script (總體代碼),setTimeout, setInterval, setImmediate, I/O, UI rendering.
micro-task: process.nextTick, Promise(原生),Object.observe,MutationObservergit

除了script總體代碼,micro-task的任務優先級高於macro-task的任務優先級。
其中,script(總體代碼) ,能夠理解爲待執行的全部代碼。github

因此執行順序以下:面試

第一步. script總體代碼被執行,執行過程爲數據庫

  • 建立setImmediate macro-tasksegmentfault

  • 建立setTimeout macro-task網絡

  • 建立micro-task Promise.then 的回調,並執行script console.log(3); resolve(); console.log(4); 此時輸出3和4,雖然resolve調用了,執行了可是總體代碼還沒執行完,沒法進入Promise.then 流程。

  • console.log(6)輸出6

  • process.nextTick 建立micro-task

  • console.log(8) 輸出8

第一個過程事後,已經輸出了3 4 6 8

第二步. 因爲其餘micro-task 的 優先級高於macro-task。
此時micro-task 中有兩個任務按照優先級process.nextTick 高於 Promise。
因此先輸出7,再輸出5

第三步,micro-task 任務列表已經執行完畢,家下來執行macro-task. 因爲setTimeout的優先級高於setIImmediate,因此先輸出2,再輸出1。

整個過程描述起來像是同步操做,其實是基於Event Loop的事件循環。

關於micro-task和macro-task的執行順序,可看下面這個例子(來自《深刻淺出Node.js》):

//加入兩個nextTick的回調函數
process.nextTick(function () {
    console.log('nextTick延遲執行1');
});
process.nextTick(function () { 
    console.log('nextTick延遲執行2');
});
// 加入兩個setImmediate()的回調函數
setImmediate(function () {
    console.log('setImmediate延遲執行1'); 
    // 進入下次循環 
    process.nextTick(function () {
        console.log('強勢插入');
    });
});
setImmediate(function () {
    console.log('setImmediate延遲執行2'); 
});

console.log('正常執行');

書中給出的執行結果是:

正常執行
nextTick延遲執行1
nextTick延遲執行2
setImmediate延遲執行1
強勢插入
setImmediate延遲執行2

process.nextTick在兩個setImmediate之間強行插入了。
但運行這段代碼發現結果倒是這樣:

正常執行
nextTick延遲執行1
nextTick延遲執行2
setImmediate延遲執行1
setImmediate延遲執行2
強勢插入

樸老師寫那本書的時候,node最新版本爲0.10.13,而個人版本是6.x

老版本的Node會優先執行process.nextTick。
當process.nextTick隊列執行完後再執行一個setImmediate任務。而後再次回到新的事件循環。因此執行完第一個setImmediate後,隊列裏只剩下第一個setImmediate裏的process.nextTick和第二個setImmediate。因此process.nextTick會先執行。

而在新版的Node中,process.nextTick執行完後,會循環遍歷setImmediate,將setImmediate都執行完畢後再跳出循環。因此兩個setImmediate執行完後隊列裏只剩下第一個setImmediate裏的process.nextTick。最後輸出"強勢插入"。

具體實現可參考Node.js源碼

關於優先級的另外一個比較清晰的版本:

觀察者優先級

在每次輪訓檢查中,各觀察者的優先級分別是:

idle觀察者 > I/O觀察者 > check觀察者。

idle觀察者:process.nextTick

I/O觀察者:通常性的I/O回調,如網絡,文件,數據庫I/O等

check觀察者:setImmediate,setTimeout

setImmediate 和 setTimeout 的優先級

看下面這個例子:

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

console.log('3');

//輸出結果是3 2 1

咱們知道如今HTML5規定setTimeout的最小間隔時間是4ms,也就是說0實際上也會別默認設置爲最小值4ms。咱們把這個延遲加大

上面說到setTimeout 的優先級比 setImmediate的高,其實這種說法是有條件的。

再看下面這個例子,爲setTimeout增長了一個延遲20ms的時間:

setImmediate(function () {
    console.log('1'); 
});
setTimeout(function () {
    console.log('2'); 
}, 20);

console.log('3');

//輸出結果是3 2 1

setTimeout延遲20ms再執行,而setImmediate是當即執行,居然2比1還先輸出??

試試打印出這個程序的執行時間:

var t1 = +new Date();
setImmediate(function () {
    console.log('1'); 
});
setTimeout(function () {
    console.log('2'); 
},20);

console.log('3');
var t2 = +new Date();
console.log('time: ' + (t2 - t1));
//輸出
3 
time: 23 
2 
1

程序執行用了23ms, 也就是說,在script(總體代碼)執行完以前,setTimeout已通過時了,因此當進入macro-task的時候setTimeout依然優先於setImmediate執行。若是咱們把這個值調大一點呢?

var t1 = +new Date();
setImmediate(function () {
    console.log('1'); 
});
setTimeout(function () {
    console.log('2'); 
},30);

console.log('3');
var t2 = +new Date();
console.log('time: ' + (t2 - t1));
//輸出
3 
time: 23 
1 
2

setImmediate早於setTimeout執行了,由於進入macro-task 循環的時候,setTimeout的定時器還沒到。

以上實驗是基於6.6.0版本Node.js測試,實際上在碰到相似這種問題的時候,最好的辦法是參考標準,並查閱源碼,不能死記概念和順序,由於標準也是會變的。包括此文也是自學總結,經供參考。

參考:
https://www.zhihu.com/questio...
https://segmentfault.com/a/11...
http://www.jianshu.com/p/837b...

相關文章
相關標籤/搜索