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

轉(zhuǎn)載自https://segmentfault.com/a/1190000008595101

『前端碎碎念』系列會(huì)記錄我平時(shí)看書或者看文章遇到的問題,一般都是比較基礎(chǔ)但是容易遺忘的知識(shí)點(diǎn),你也可能會(huì)在面試中碰到。 我會(huì)查閱一些資料并可能加上自己的理解,來記錄這些問題。更多文章請(qǐng)前往我的個(gè)人博客

這個(gè)問題是有關(guān)執(zhí)行順序和Event Loop的。關(guān)于Event Loop和任務(wù)隊(duì)列等概念,可以先閱讀我引用中的文章,本文主要分析一些存在的疑惑點(diǎn)。

下面這個(gè)例子比較典型:

setImmediate(function(){console.log(1);},0);setTimeout(function(){console.log(2);},0);newPromise(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);//輸出結(jié)果是3 4 6 8 7 5 2 1

在解釋輸出結(jié)果之前,我們來看幾個(gè)概念:

macro-task: script (整體代碼),setTimeout, setInterval, setImmediate, I/O, UI rendering.

micro-task: process.nextTick, Promise(原生),Object.observe,MutationObserver

除了script整體代碼,micro-task的任務(wù)優(yōu)先級(jí)高于macro-task的任務(wù)優(yōu)先級(jí)。其中,script(整體代碼) ,可以理解為待執(zhí)行的所有代碼。

所以執(zhí)行順序如下:

第一步. script整體代碼被執(zhí)行,執(zhí)行過程為

創(chuàng)建setImmediate macro-task

創(chuàng)建setTimeout macro-task

創(chuàng)建micro-task Promise.then 的回調(diào),并執(zhí)行script console.log(3); resolve(); console.log(4); 此時(shí)輸出3和4,雖然resolve調(diào)用了,執(zhí)行了但是整體代碼還沒執(zhí)行完,無(wú)法進(jìn)入Promise.then 流程。

console.log(6)輸出6

process.nextTick 創(chuàng)建micro-task

console.log(8) 輸出8

第一個(gè)過程過后,已經(jīng)輸出了3 4 6 8

第二步. 由于其他micro-task 的 優(yōu)先級(jí)高于macro-task。

此時(shí)micro-task 中有兩個(gè)任務(wù)按照優(yōu)先級(jí)process.nextTick 高于 Promise。

所以先輸出7,再輸出5

第三步,micro-task 任務(wù)列表已經(jīng)執(zhí)行完畢,家下來執(zhí)行macro-task. 由于setTimeout的優(yōu)先級(jí)高于setIImmediate,所以先輸出2,再輸出1。

整個(gè)過程描述起來像是同步操作,實(shí)際上是基于Event Loop的事件循環(huán)。

關(guān)于micro-task和macro-task的執(zhí)行順序,可看下面這個(gè)例子(來自《深入淺出Node.js》):

//加入兩個(gè)nextTick的回調(diào)函數(shù)process.nextTick(function(){console.log('nextTick延遲執(zhí)行1');});process.nextTick(function(){console.log('nextTick延遲執(zhí)行2');});// 加入兩個(gè)setImmediate()的回調(diào)函數(shù)setImmediate(function(){console.log('setImmediate延遲執(zhí)行1');// 進(jìn)入下次循環(huán)process.nextTick(function(){console.log('強(qiáng)勢(shì)插入');? ? });});setImmediate(function(){console.log('setImmediate延遲執(zhí)行2'); });console.log('正常執(zhí)行');

書中給出的執(zhí)行結(jié)果是:

正常執(zhí)行nextTick延遲執(zhí)行1nextTick延遲執(zhí)行2setImmediate延遲執(zhí)行1強(qiáng)勢(shì)插入setImmediate延遲執(zhí)行2

process.nextTick在兩個(gè)setImmediate之間強(qiáng)行插入了。但運(yùn)行這段代碼發(fā)現(xiàn)結(jié)果卻是這樣:

正常執(zhí)行nextTick延遲執(zhí)行1nextTick延遲執(zhí)行2setImmediate延遲執(zhí)行1setImmediate延遲執(zhí)行2強(qiáng)勢(shì)插入

樸老師寫那本書的時(shí)候,node最新版本為0.10.13,而我的版本是6.x

老版本的Node會(huì)優(yōu)先執(zhí)行process.nextTick。當(dāng)process.nextTick隊(duì)列執(zhí)行完后再執(zhí)行一個(gè)setImmediate任務(wù)。然后再次回到新的事件循環(huán)。所以執(zhí)行完第一個(gè)setImmediate后,隊(duì)列里只剩下第一個(gè)setImmediate里的process.nextTick和第二個(gè)setImmediate。所以process.nextTick會(huì)先執(zhí)行。

而在新版的Node中,process.nextTick執(zhí)行完后,會(huì)循環(huán)遍歷setImmediate,將setImmediate都執(zhí)行完畢后再跳出循環(huán)。所以兩個(gè)setImmediate執(zhí)行完后隊(duì)列里只剩下第一個(gè)setImmediate里的process.nextTick。最后輸出"強(qiáng)勢(shì)插入"。

具體實(shí)現(xiàn)可參考Node.js源碼

關(guān)于優(yōu)先級(jí)的另一個(gè)比較清晰的版本:

觀察者優(yōu)先級(jí)

在每次輪訓(xùn)檢查中,各觀察者的優(yōu)先級(jí)分別是:

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

idle觀察者:process.nextTick

I/O觀察者:一般性的I/O回調(diào),如網(wǎng)絡(luò),文件,數(shù)據(jù)庫(kù)I/O等

check觀察者:setImmediate,setTimeout

setImmediate 和 setTimeout 的優(yōu)先級(jí)

看下面這個(gè)例子:

setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },0);console.log('3');//輸出結(jié)果是3 2 1

我們知道現(xiàn)在HTML5規(guī)定setTimeout的最小間隔時(shí)間是4ms,也就是說0實(shí)際上也會(huì)別默認(rèn)設(shè)置為最小值4ms。我們把這個(gè)延遲加大

上面說到setTimeout 的優(yōu)先級(jí)比 setImmediate的高,其實(shí)這種說法是有條件的。

再看下面這個(gè)例子,為setTimeout增加了一個(gè)延遲20ms的時(shí)間:

setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },20);console.log('3');//輸出結(jié)果是3 2 1

setTimeout延遲20ms再執(zhí)行,而setImmediate是立即執(zhí)行,竟然2比1還先輸出??

試試打印出這個(gè)程序的執(zhí)行時(shí)間:

vart1 = +newDate();setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },20);console.log('3');vart2 = +newDate();console.log('time: '+ (t2 - t1));//輸出3time:2321

程序執(zhí)行用了23ms, 也就是說,在script(整體代碼)執(zhí)行完之前,setTimeout已經(jīng)過時(shí)了,所以當(dāng)進(jìn)入macro-task的時(shí)候setTimeout依然優(yōu)先于setImmediate執(zhí)行。如果我們把這個(gè)值調(diào)大一點(diǎn)呢?

vart1 = +newDate();setImmediate(function(){console.log('1'); });setTimeout(function(){console.log('2'); },30);console.log('3');vart2 = +newDate();console.log('time: '+ (t2 - t1));//輸出3time:2312

setImmediate早于setTimeout執(zhí)行了,因?yàn)檫M(jìn)入macro-task 循環(huán)的時(shí)候,setTimeout的定時(shí)器還沒到。

以上實(shí)驗(yàn)是基于6.6.0版本Node.js測(cè)試,實(shí)際上在碰到類似這種問題的時(shí)候,最好的辦法是參考標(biāo)準(zhǔn),并查閱源碼,不能死記概念和順序,因?yàn)闃?biāo)準(zhǔn)也是會(huì)變的。包括此文也是自學(xué)總結(jié),經(jīng)供參考。

參考:

https://www.zhihu.com/questio...

https://segmentfault.com/a/11...

http://www.lxweimin.com/p/837b...

3月7日發(fā)布

更多

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,398評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容