本篇文章通過圖文結(jié)合的形式來帶大家搞懂Nodejs中的事件循環(huán),希望對大家有所幫助!
以下全文7000字,請?jiān)谀?strong>思路清晰、精力充沛的時(shí)刻觀看。保證你理解后很長時(shí)間忘不掉?!就扑]學(xué)習(xí):《nodejs 教程》】
Node事件循環(huán)
Node底層使用的語言libuv,是一個(gè)c++語言。他用來操作底層的操作系統(tǒng),封裝了操作系統(tǒng)的接口。Node的事件循環(huán)也是用libuv
來寫的,所以Node生命周期和瀏覽器的還是有區(qū)別的。
因?yàn)镹ode和操作系統(tǒng)打交道,所以事件循環(huán)比較復(fù)雜,也有一些自己特有的API。
事件循環(huán)在不同的操作系統(tǒng)里有一些細(xì)微的差異。這將涉及到操作系統(tǒng)的知識,暫時(shí)不表。 本次只介紹JS主線程中,Node的運(yùn)作流程。Node的其他線程暫時(shí)也不擴(kuò)展。
事件循環(huán)圖
說好的一張圖,也不賣關(guān)子。下邊這張圖搞清楚了,事件循環(huán)就學(xué)會了。
事件循環(huán)圖
事件循環(huán)圖-結(jié)構(gòu)
為了讓大家先有個(gè)大局觀,先貼一張目錄結(jié)構(gòu)圖在前邊:
目錄
接下來詳細(xì)展開說說
主線程
主線程
上圖中,幾個(gè)色塊的含義:
main
:啟動入口文件,運(yùn)行主函數(shù)event loop
:檢查是否要進(jìn)入事件循環(huán)- 檢查其他線程里是否還有待處理事項(xiàng)
- 檢查其他任務(wù)是否還在進(jìn)行中(比如計(jì)時(shí)器、文件讀取操作等任務(wù)是否完成)
- 有以上情況,進(jìn)入事件循環(huán),運(yùn)行其他任務(wù)
事件循環(huán)的過程:沿著從timers到close callbacks這個(gè)流程,走一圈。到event loop看是否結(jié)束,沒結(jié)束再走一圈。
over
:所有的事情都完畢,結(jié)束
事件循環(huán) 圈
事件循環(huán) 圈
圖中灰色的圈跟操作系統(tǒng)有關(guān)系,不是本章解析重點(diǎn)。重點(diǎn)關(guān)注黃色、橙色的圈還有中間橘黃的方框。
我們把每一圈的事件循環(huán)叫做「一次循環(huán)」、又叫「一次輪詢」、又叫「一次Tick」。
一次循環(huán)要經(jīng)過六個(gè)階段:
-
timers:計(jì)時(shí)器(setTimeout、setInterval等的回調(diào)函數(shù)存放在里邊)
-
pending callback
-
idle prepare
-
poll:輪詢隊(duì)列(除timers、check之外的回調(diào)存放在這里)
-
check:檢查階段(使用 setImmediate 的回調(diào)會直接進(jìn)入這個(gè)隊(duì)列)
-
close callbacks
本次我們只關(guān)注上邊標(biāo)紅的三個(gè)重點(diǎn)。
工作原理
- 每一個(gè)階段都會維護(hù)一個(gè)事件隊(duì)列??梢园衙恳粋€(gè)圈想象成一個(gè)事件隊(duì)列。
- 這就和瀏覽器不一樣了,瀏覽器最多兩個(gè)隊(duì)列(宏隊(duì)列、微隊(duì)列)。但是在node里邊有六個(gè)隊(duì)列
- 到達(dá)一個(gè)隊(duì)列后,檢查隊(duì)列內(nèi)是否有任務(wù)(也就是看下是否有回調(diào)函數(shù))需要執(zhí)行。如果有,就依次執(zhí)行,直到全部執(zhí)行完畢、清空隊(duì)列。
- 如果沒有任務(wù),進(jìn)入下一個(gè)隊(duì)列去檢查。直到所有隊(duì)列檢查一遍,算一個(gè)輪詢。
- 其中,
timers
、pending callback
、idle prepare
等執(zhí)行完畢后,到達(dá)poll
隊(duì)列。
timers隊(duì)列的工作原理
timers并非真正意義上的隊(duì)列,他內(nèi)部存放的是計(jì)時(shí)器。
每次到達(dá)這個(gè)隊(duì)列,會檢查計(jì)時(shí)器線程內(nèi)的所有計(jì)時(shí)器,計(jì)時(shí)器線程內(nèi)部多個(gè)計(jì)時(shí)器按照時(shí)間順序排序。
檢查過程:將每一個(gè)計(jì)時(shí)器按順序分別計(jì)算一遍,計(jì)算該計(jì)時(shí)器開始計(jì)時(shí)的時(shí)間到當(dāng)前時(shí)間是否滿足計(jì)時(shí)器的間隔參數(shù)設(shè)定(比如1000ms,計(jì)算計(jì)時(shí)器開始計(jì)時(shí)到現(xiàn)在是否有1m)。當(dāng)某個(gè)計(jì)時(shí)器檢查通過,則執(zhí)行其回調(diào)函數(shù)。
poll隊(duì)列的運(yùn)作方式
- 如果poll中有回調(diào)函數(shù)需要執(zhí)行,依次執(zhí)行回調(diào),直到清空隊(duì)列。
- 如果poll中沒有回調(diào)函數(shù)需要執(zhí)行,已經(jīng)是空隊(duì)列了。則會在這里等待,等待其他隊(duì)列中出現(xiàn)回調(diào),
- 如果其他隊(duì)列中出現(xiàn)回調(diào),則從poll向下到over,結(jié)束該階段,進(jìn)入下一階段。
- 如果其他隊(duì)列也都沒有回調(diào),則持續(xù)在poll隊(duì)列等待,直到任何一個(gè)隊(duì)列出現(xiàn)回調(diào)后再進(jìn)行工作。(是個(gè)小懶蟲的處事方式)
舉例梳理事件流程
setTimeout(() => { console.log('object'); }, 5000) console.log('node');
以上代碼的事件流程梳理
- 進(jìn)入主線程,執(zhí)行setTimeout(),回調(diào)函數(shù)作為異步任務(wù)被放入異步隊(duì)列timers隊(duì)列中,暫時(shí)不執(zhí)行。
- 繼續(xù)向下,執(zhí)行定時(shí)器后邊的console,打印“node”。
- 判斷是否有事件循環(huán)。是,走一圈輪詢:從timers – pending callback – idle prepare……
- 到poll隊(duì)列停下循環(huán)并等待。
- 由于這時(shí)候沒到5秒,timers隊(duì)列無任務(wù),所以一直在poll隊(duì)列卡著,同時(shí)輪詢檢查其他隊(duì)列是否有任務(wù)。
- 等5秒到達(dá),setTimeout的回調(diào)塞到timers內(nèi),例行輪詢檢查到timers隊(duì)列有任務(wù),則向下走,經(jīng)過check、close callbacks后到達(dá)timers。將timers隊(duì)列清空。
- 繼續(xù)輪詢到poll等待,詢問是否還需要event loop,不需要,則到達(dá)over結(jié)束。
要理解這個(gè)問題,看下邊的代碼及流程解析:
setTimeout(function t1() { console.log('setTimeout'); }, 5000) console.log('node 生命周期'); const http = require('http') const server = http.createServer(function h1() { console.log('請求回調(diào)'); }); server.listen(8080)
代碼分析如下:
- 照舊,先執(zhí)行主線程,打印“node 生命周期”、引入http后創(chuàng)建http服務(wù)。
- 然后event loop檢查是否有異步任務(wù),檢查發(fā)現(xiàn)有定時(shí)器任務(wù)和請求任務(wù)。所以進(jìn)入事件循環(huán)。
-
六個(gè)隊(duì)列都沒任務(wù),則在poll隊(duì)列等待。如下圖:
- 過了五秒,timers中有了任務(wù),則流程從poll放行向下,經(jīng)過check和close callbacks隊(duì)列后,到達(dá)event loop。
- event loop檢查是否有異步任務(wù),檢查發(fā)現(xiàn)有定時(shí)器任務(wù)和請求任務(wù)。所以再次進(jìn)入事件循環(huán)。
-
到達(dá)timers隊(duì)列,發(fā)現(xiàn)有回調(diào)函數(shù)任務(wù),則依次執(zhí)行回調(diào),清空timers隊(duì)列(當(dāng)然這里只有一個(gè)5秒到達(dá)后的回調(diào),所以直接執(zhí)行完了即可),打印出“setTimeout”。如下圖
- 清空timers隊(duì)列后,輪詢繼續(xù)向下到達(dá)poll隊(duì)列,由于poll隊(duì)列現(xiàn)在是空隊(duì)列,所以在這里等待。
- 后來,假設(shè)用戶請求發(fā)來了,h1回調(diào)函數(shù)被放到poll隊(duì)列。于是poll中有回調(diào)函數(shù)需要執(zhí)行,依次執(zhí)行回調(diào),直到清空poll隊(duì)列。
-
poll隊(duì)列清空,此時(shí)poll隊(duì)列是空隊(duì)列,繼續(xù)等待。
- 由于node線程一直holding在poll隊(duì)列,等很長一段時(shí)間還是沒有任務(wù)來臨時(shí),會自動斷開等待(不自信表現(xiàn)),向下執(zhí)行輪詢流程,經(jīng)過check、close callbacks后到達(dá)event loop
- 到了event loop后,檢查是否有異步任務(wù),檢查發(fā)現(xiàn)有請求任務(wù)。(此時(shí)定時(shí)器任務(wù)已經(jīng)執(zhí)行完畢,所以沒有了),則繼續(xù)再次進(jìn)入事件循環(huán)。
- 到達(dá)poll隊(duì)列,再次holding……
- 再等很長時(shí)間沒有任務(wù)來臨,自動斷開到even loop(再補(bǔ)充一點(diǎn)無任務(wù)的循環(huán)情況)
- 再次回到poll隊(duì)列掛起
- 無限循環(huán)……
梳理事件循環(huán)流程圖:
注意:下圖中的“是否有任務(wù)”的說法表示“是否有本隊(duì)列的任務(wù)”。
event loop流程梳理
再用一個(gè)典型的例子驗(yàn)證下流程:
const startTime = new Date(); setTimeout(function f1() { console.log('setTimeout', new Date(), new Date() - startTime); }, 200) console.log('node 生命周期', startTime); const fs = require('fs') fs.readFile('./poll.js', 'utf-8', function fsFunc(err, data) { const fsTime = new Date() console.log('fs', fsTime); while (new Date() - fsTime < 300) { } console.log('結(jié)束死循環(huán)', new Date()); });
連續(xù)運(yùn)行三遍,打印結(jié)果如下:
執(zhí)行流程解析:
-
執(zhí)行全局上下文,打印「node 生命周期 + 時(shí)間」
-
詢問是否有event loop
-
有,進(jìn)入timers隊(duì)列,檢查沒有計(jì)時(shí)器(cpu處理速度可以,這時(shí)還沒到200ms)
-
輪詢進(jìn)入到poll,讀文件還沒讀完(比如此時(shí)才用了20ms),因此poll隊(duì)列是空的,也沒有任務(wù)回調(diào)
-
在poll隊(duì)列等待……不斷輪詢看有沒有回調(diào)
-
文件讀完,poll隊(duì)列有了fsFunc回調(diào)函數(shù),并且被執(zhí)行,輸出「fs + 時(shí)間」
-
在while死循環(huán)那里卡300毫秒,
-
死循環(huán)卡到200ms的時(shí)候,f1回調(diào)進(jìn)入timers隊(duì)列。但此時(shí)poll隊(duì)列很忙,占用了線程,不會向下執(zhí)行。
-
直到300ms后poll隊(duì)列清空,輸出「結(jié)束死循環(huán) + 時(shí)間」
-
event loop趕緊向下走
-
再來一輪到timers,執(zhí)行timers隊(duì)列里的f1回調(diào)。于是看到「setTimeout + 時(shí)間」
-
timers隊(duì)列清空,回到poll隊(duì)列,沒有任務(wù),等待一會。
-
等待時(shí)間夠長后,向下回到event loop。
-
event loop檢查沒有其他異步任務(wù)了,結(jié)束線程,整個(gè)程序over退出。
check 階段
檢查階段(使用 setImmediate 的回調(diào)會直接進(jìn)入這個(gè)隊(duì)列)
check隊(duì)列的實(shí)際工作原理
真正的隊(duì)列,里邊扔的就是待執(zhí)行的回調(diào)函數(shù)的集合。類似[fn,fn]這種形式的。
每次到達(dá)check這個(gè)隊(duì)列后,立即按順序執(zhí)行回調(diào)函數(shù)即可【類似于[fn1,fn2].forEach((fn)=>fn())的感覺】
所以說,setImmediate不是一個(gè)計(jì)時(shí)器的概念。
如果你去面試,涉及到Node環(huán)節(jié),可能會遇到下邊這個(gè)問題:setImmediate和setTimeout(0)誰更快。
setImmediate() 與 setTimeout(0) 的對比
- setImmediate的回調(diào)是異步的,和setTimeout回調(diào)性質(zhì)一致。
- setImmediate回調(diào)在
check
隊(duì)列,setTimeout回調(diào)在timers
隊(duì)列(概念意義,實(shí)際在計(jì)時(shí)器線程,只是setTimeout在timers隊(duì)列做檢查調(diào)用而已。詳細(xì)看timers的工作原理)。 - setImmediate函數(shù)調(diào)用后,回調(diào)函數(shù)會立即push到check隊(duì)列,并在下次eventloop時(shí)被執(zhí)行。setTimeout函數(shù)調(diào)用后,計(jì)時(shí)器線程增加一個(gè)定時(shí)器任務(wù),下次eventloop時(shí)會在timers階段里檢查判斷定時(shí)器任務(wù)是否到達(dá)時(shí)間,到了則執(zhí)行回調(diào)函數(shù)。
- 綜上,setImmediate的運(yùn)算速度比setTimeout(0)的要快,因?yàn)閟etTimeout還需要開計(jì)時(shí)器線程,并增加計(jì)算的開銷。
二者的效果差不多。但是執(zhí)行順序不定
觀察以下代碼:
setTimeout(() => { console.log('setTimeout'); }, 0); setImmediate(() => { console.log('setImmediate'); });
多次反復(fù)運(yùn)行,執(zhí)行效果如下:
順序不定
可以看到多次運(yùn)行,兩句console.log打印的順序不定。
這是因?yàn)閟etTimeout的間隔數(shù)最小填1,雖然下邊代碼填了0。但實(shí)際計(jì)算機(jī)執(zhí)行當(dāng)1ms算。(這里注意和瀏覽器的計(jì)時(shí)器區(qū)分。在瀏覽器中,setInterval的最小間隔數(shù)為10ms,小于10ms則會被設(shè)置為10;設(shè)備供電狀態(tài)下,間隔最小為16.6ms。)
以上代碼,主線程運(yùn)行的時(shí)候,setTimeout函數(shù)調(diào)用,計(jì)時(shí)器線程增加一個(gè)定時(shí)器任務(wù)。setImmediate函數(shù)調(diào)用后,其回調(diào)函數(shù)立即push到check隊(duì)列。主線程執(zhí)行完畢。
eventloop判斷時(shí),發(fā)現(xiàn)timers和check隊(duì)列有內(nèi)容,進(jìn)入異步輪詢:
第一種情況:等到了timers里這段時(shí)間,可能還沒有1ms的時(shí)間,定時(shí)器任務(wù)間隔時(shí)間的條件不成立所以timers里還沒有回調(diào)函數(shù)。繼續(xù)向下到了check隊(duì)列里,這時(shí)候setImmediate的回調(diào)函數(shù)早已等候多時(shí),直接執(zhí)行。而再下次eventloop到達(dá)timers隊(duì)列,定時(shí)器也早已成熟,才會執(zhí)行setTimeout的回調(diào)任務(wù)。于是順序就是「setImmediate -> setTimeout」。
第二種情況:但也有可能到了timers階段時(shí),超過了1ms。于是計(jì)算定時(shí)器條件成立,setTimeout的回調(diào)函數(shù)被直接執(zhí)行。eventloop再向下到達(dá)check隊(duì)列執(zhí)行setImmediate的回調(diào)。最終順序就是「setTimeout -> setImmediate」了。
所以,只比較這兩個(gè)函數(shù)的情況下,二者的執(zhí)行順序最終結(jié)果取決于當(dāng)下計(jì)算機(jī)的運(yùn)行環(huán)境以及運(yùn)行速度。
二者時(shí)間差距的對比代碼
------------------setTimeout測試:------------------- let i = 0; console.time('setTimeout'); function test() { if (i < 1000) { setTimeout(test, 0) i++ } else { console.timeEnd('setTimeout'); } } test(); ------------------setImmediate測試:------------------- let i = 0; console.time('setImmediate'); function test() { if (i < 1000) { setImmediate(test) i++ } else { console.timeEnd('setImmediate'); } } test();
運(yùn)行觀察時(shí)間差距:
setTimeout與setImmediate時(shí)間差距
可見setTimeout遠(yuǎn)比setImmediate耗時(shí)多得多
這是因?yàn)閟etTimeout不僅有主代碼執(zhí)行的時(shí)間消耗。還有在timers隊(duì)列里,對于計(jì)時(shí)器線程中各個(gè)定時(shí)任務(wù)的計(jì)算時(shí)間。
結(jié)合poll隊(duì)列的面試題(考察timers、poll和check的執(zhí)行順序)
如果你看懂了上邊的事件循環(huán)圖,下邊這道題難不倒你!
// 說說下邊代碼的執(zhí)行順序,先打印哪個(gè)? const fs = require('fs') fs.readFile('./poll.js', () => { setTimeout(() => console.log('setTimeout'), 0) setImmediate(() => console.log('setImmediate')) })
上邊這種代碼邏輯,不管執(zhí)行多少次,肯定都是先執(zhí)行setImmediate。
先執(zhí)行setImmediate
因?yàn)閒s各個(gè)函數(shù)的回調(diào)是放在poll隊(duì)列的。當(dāng)程序holding在poll隊(duì)列后,出現(xiàn)回調(diào)立即執(zhí)行。
回調(diào)內(nèi)執(zhí)行setTimeout和setImmediate的函數(shù)后,check隊(duì)列立即增加了回調(diào)。
回調(diào)執(zhí)行完畢,輪詢檢查其他隊(duì)列有內(nèi)容,程序結(jié)束poll隊(duì)列的holding向下執(zhí)行。
check是poll階段的緊接著的下一個(gè)。所以在向下的過程中,先執(zhí)行check階段內(nèi)的回調(diào),也就是先打印setImmediate。
到下一輪循環(huán),到達(dá)timers隊(duì)列,檢查setTimeout計(jì)時(shí)器符合條件,則定時(shí)器回調(diào)被執(zhí)行。
nextTick 與 Promise
說完宏任務(wù),接下來說下微任務(wù)
- 二者都是「微隊(duì)列」,執(zhí)行異步微任務(wù)。
- 二者不是事件循環(huán)的一部分,程序也不會開啟額外的線程去處理相關(guān)任務(wù)。(理解:promise里發(fā)網(wǎng)絡(luò)請求,那是網(wǎng)絡(luò)請求開的網(wǎng)絡(luò)線程,跟Promise這個(gè)微任務(wù)沒關(guān)系)
- 微隊(duì)列設(shè)立的目的就是讓一些任務(wù)「馬上」、「立即」優(yōu)先執(zhí)行。
- nextTick與Promise比較,nextTick的級別更高。
nextTick表現(xiàn)形式
process.nextTick(() => {})
Promise表現(xiàn)形式
Promise.resolve().then(() => {})
如何參與事件循環(huán)?
事件循環(huán)中,每執(zhí)行一個(gè)回調(diào)前,先按序清空一次nextTick和promise。
// 先思考下列代碼的執(zhí)行順序 setImmediate(() => { console.log('setImmediate'); }); process.nextTick(() => { console.log('nextTick 1'); process.nextTick(() => { console.log('nextTick 2'); }) }) console.log('global'); Promise.resolve().then(() => { console.log('promise 1'); process.nextTick(() => { console.log('nextTick in promise'); }) })
最終順序:
-
global
-
nextTick 1
-
nextTick 2
-
promise 1
-
nextTick in promise
-
setImmediate
兩個(gè)問題:
基于上邊的說法,有兩個(gè)問題待思考和解決:
-
每走一個(gè)異步宏任務(wù)隊(duì)列就查一遍nextTick和promise?還是每執(zhí)行完 宏任務(wù)隊(duì)列里的一個(gè)回調(diào)函數(shù)就查一遍呢?
-
如果在poll的holding階段,插入一個(gè)nextTick或者Promise的回調(diào),會立即停止poll隊(duì)列的holding去執(zhí)行回調(diào)嗎?
上邊兩個(gè)問題,看下邊代碼的說法
setTimeout(() => { console.log('setTimeout 100'); setTimeout(() => { console.log('setTimeout 100 - 0'); process.nextTick(() => { console.log('nextTick in setTimeout 100 - 0'); }) }, 0) setImmediate(() => { console.log('setImmediate in setTimeout 100'); process.nextTick(() => { console.log('nextTick in setImmediate in setTimeout 100'); }) }); process.nextTick(() => { console.log('nextTick in setTimeout100'); }) Promise.resolve().then(() => { console.log('promise in setTimeout100'); }) }, 100) const fs = require('fs') fs.readFile('./1.poll.js', () => { console.log('poll 1'); process.nextTick(() => { console.log('nextTick in poll ======'); }) }) setTimeout(() => { console.log('setTimeout 0'); process.nextTick(() => { console.log('nextTick in setTimeout'); }) }, 0) setTimeout(() => { console.log('setTimeout 1'); Promise.resolve().then(() => { console.log('promise in setTimeout1'); }) process.nextTick(() => { console.log('nextTick in setTimeout1'); }) }, 1) setImmediate(() => { console.log('setImmediate'); process.nextTick(() => { console.log('nextTick in setImmediate'); }) }); process.nextTick(() => { console.log('nextTick 1'); process.nextTick(() => { console.log('nextTick 2'); }) }) console.log('global ------'); Promise.resolve().then(() => { console.log('promise 1'); process.nextTick(() => { console.log('nextTick in promise'); }) }) /** 執(zhí)行順序如下 global ------ nextTick 1 nextTick 2 promise 1 nextTick in promise setTimeout 0 // 解釋問題1. 沒有上邊的nextTick和promise,setTimeout和setImmediate的順序不一定,有了以后肯定是0先開始。 // 可見,執(zhí)行一個(gè)隊(duì)列之前,就先檢查并執(zhí)行了nextTick和promise微隊(duì)列 nextTick in setTimeout setTimeout 1 nextTick in setTimeout1 promise in setTimeout1 setImmediate nextTick in setImmediate poll 1 nextTick in poll ====== setTimeout 100 nextTick in setTimeout100 promise in setTimeout100 setImmediate in setTimeout 100 nextTick in setImmediate in setTimeout 100 setTimeout 100 - 0 nextTick in setTimeout 100 - 0 */
以上代碼執(zhí)行多次,順序不變,setTimeout和setImmediate的順序都沒變。
執(zhí)行順序及具體原因說明如下:
-
global
:主線程同步任務(wù),率先執(zhí)行沒毛病 -
nextTick 1
:執(zhí)行異步宏任務(wù)之前,清空異步微任務(wù),nextTick優(yōu)先級高,先行一步 -
nextTick 2
:執(zhí)行完上邊這句代碼,又一個(gè)nextTick微任務(wù),立即率先執(zhí)行 -
promise 1
:執(zhí)行異步宏任務(wù)之前,清空異步微任務(wù),Promise的優(yōu)先級低,所以在nextTick完了以后立即執(zhí)行 -
nextTick in promise
:清空Promise隊(duì)列的過程中,遇到nextTick微任務(wù),立即執(zhí)行、清空 -
setTimeout 0
: 解釋第一個(gè)問題. 沒有上邊的nextTick和promise,只有setTimeout和setImmediate時(shí)他倆的執(zhí)行順序不一定。有了以后肯定是0先開始??梢姡瑘?zhí)行一個(gè)宏隊(duì)列之前,就先按順序檢查并執(zhí)行了nextTick和promise微隊(duì)列。等微隊(duì)列全部執(zhí)行完畢,setTimeout(0)的時(shí)機(jī)也成熟了,就被執(zhí)行。 -
nextTick in setTimeout
:執(zhí)行完上邊這句代碼,又一個(gè)nextTick微任務(wù),立即率先執(zhí)行 【這種回調(diào)函數(shù)里的微任務(wù),我不能確定是緊隨同步任務(wù)執(zhí)行的;還是放到微任務(wù)隊(duì)列,等下一個(gè)宏任務(wù)執(zhí)行前再清空的他們。但是順序看上去和立即執(zhí)行他們一樣。不過我比較傾向于是后者:先放到微任務(wù)隊(duì)列等待,下一個(gè)宏任務(wù)執(zhí)行前清空他們?!?/em> -
setTimeout 1
:因?yàn)閳?zhí)行微任務(wù)耗費(fèi)時(shí)間,導(dǎo)致此時(shí)timers里判斷兩個(gè)0和1的setTimeout計(jì)時(shí)器已經(jīng)結(jié)束,所以兩個(gè)setTimeout回調(diào)都已加入隊(duì)列并被執(zhí)行 -
nextTick in setTimeout1
:執(zhí)行完上邊這句代碼,又一個(gè)nextTick微任務(wù),立即率先執(zhí)行 【可能是下一個(gè)宏任務(wù)前清空微任務(wù)】 -
promise in setTimeout1
:執(zhí)行完上邊這句代碼,又一個(gè)Promise微任務(wù),立即緊隨執(zhí)行 【可能是下一個(gè)宏任務(wù)前清空微任務(wù)】 -
setImmediate
:poll隊(duì)列回調(diào)時(shí)機(jī)未到,先行向下到check隊(duì)列,清空隊(duì)列,立即執(zhí)行setImmediate回調(diào) -
nextTick in setImmediate
:執(zhí)行完上邊這句代碼,又一個(gè)nextTick微任務(wù),立即率先執(zhí)行 【可能是下一個(gè)宏任務(wù)前清空微任務(wù)】 -
poll 1
:poll隊(duì)列實(shí)際成熟,回調(diào)觸發(fā),同步任務(wù)執(zhí)行。 -
nextTick in poll
:執(zhí)行完上邊這句代碼,又一個(gè)nextTick微任務(wù),立即率先執(zhí)行 【可能是下一個(gè)宏任務(wù)前清空微任務(wù)】 -
setTimeout 100
:定時(shí)器任務(wù)到達(dá)時(shí)間,執(zhí)行回調(diào)。并在回調(diào)里往微任務(wù)推入了nextTick、Promise,往宏任務(wù)的check里推入了setImmediate的回調(diào)。并且也開啟了計(jì)時(shí)器線程,往timers里增加了下一輪回調(diào)的可能。 -
nextTick in setTimeout100
:宏任務(wù)向下前,率先執(zhí)行定時(shí)器回調(diào)內(nèi)新增的微任務(wù)-nextTick 【這里就能確定了,是下一個(gè)宏任務(wù)前清空微任務(wù)的流程】 -
promise in setTimeout100
:緊接著執(zhí)行定時(shí)器回調(diào)內(nèi)新增的微任務(wù)-Promise 【清空完nextTick清空Promise的順序】 -
setImmediate in setTimeout 100
:這次setImmediate比setTimeout(0)先執(zhí)行的原因是:流程從timers向后走到check隊(duì)列,已經(jīng)有了setImmediate的回調(diào),立即執(zhí)行。 -
nextTick in setImmediate in setTimeout 100
:執(zhí)行完上邊這句代碼,又一個(gè)nextTick微任務(wù),下一個(gè)宏任務(wù)前率先清空微任務(wù) -
setTimeout 100 - 0
:輪詢又一次回到timers,執(zhí)行100-0的回調(diào)。 -
nextTick in setTimeout 100 - 0
:執(zhí)行完上邊這句代碼,又一個(gè)nextTick微任務(wù),下一個(gè)宏任務(wù)前率先清空微任務(wù)。
擴(kuò)展:為什么有了setImmediate還要有nextTick和Promise?
一開始設(shè)計(jì)的時(shí)候,setImmediate充當(dāng)了微隊(duì)列的作用(雖然他不是)。設(shè)計(jì)者希望執(zhí)行完poll后立即執(zhí)行setImmediate(當(dāng)然現(xiàn)在也確實(shí)是這么表現(xiàn)的)。所以起的名字叫Immediate
,表示立即
的意思。 但是后來問題是,poll里可能有N個(gè)任務(wù)連續(xù)執(zhí)行,在執(zhí)行期間想要執(zhí)行setImmediate是不可能的。因?yàn)閜oll隊(duì)列不停,流程不向下執(zhí)行。
于是出現(xiàn)nextTick,真正的微隊(duì)列概念。但此時(shí),immediate的名字被占用了,所以名字叫nextTick(下一瞬間)。事件循環(huán)期間,執(zhí)行任何一個(gè)隊(duì)列之前,都要檢查他是否被清空。其次是Promise。
面試題
最后,檢驗(yàn)學(xué)習(xí)成果的面試題來了
async function async1() { console.log('async start'); await async2(); console.log('async end'); } async function async2(){ console.log('async2'); } console.log('script start'); setTimeout(() => { console.log('setTimeout 0'); }, 0) setTimeout(() => { console.log('setTimeout 3'); }, 3) setImmediate(() => { console.log('setImmediate'); }) process.nextTick(() => { console.log('nextTick'); }) async1(); new Promise((res) => { console.log('promise1'); res(); console.log('promise2'); }).then(() => { console.log('promise 3'); }); console.log('script end'); // 答案如下 // - // - // - // - // - // - // - // - // - // - // - // - /** script start async start async2 promise1 promise2 script end nextTick async end promise 3 // 后邊這仨的運(yùn)行順序就是驗(yàn)證你電腦運(yùn)算速度的時(shí)候了。 速度最好(執(zhí)行上邊的同步代碼 + 微任務(wù) + 計(jì)時(shí)器運(yùn)算用了不到0ms): setImmediate setTimeout 0 setTimeout 3 速度中等(執(zhí)行上邊的同步代碼 + 微任務(wù) + 計(jì)時(shí)器運(yùn)算用了0~3ms以上): setTimeout 0 setImmediate setTimeout 3 速度較差(執(zhí)行上邊的同步代碼 + 微任務(wù) + 計(jì)時(shí)器運(yùn)算用了3ms以上): setTimeout 0 setTimeout 3 setImmediate */
思維腦圖 – Node生命周期核心階段