node事件循環(huán)中事件執(zhí)行的順序
事件循環(huán)
在瀏覽器環(huán)境下我們的js有一套自己的事件循環(huán),同樣在node環(huán)境下也有一套類似的事件循環(huán)。
瀏覽器環(huán)境事件循環(huán)
首先,我們先來(lái)回顧一下在瀏覽器的事件循環(huán):
總結(jié)來(lái)說(shuō):
首先會(huì)運(yùn)行主線程的同步代碼,每一行同步代碼都會(huì)被壓入執(zhí)行棧,每一行異步代碼會(huì)壓入異步API中(如:定時(shí)器線程、ajax線程等;),在執(zhí)行棧沒(méi)有要執(zhí)行的代碼時(shí),也就是我們當(dāng)前主線程沒(méi)有同步代碼了,任務(wù)隊(duì)列會(huì)從我們的異步任務(wù)微任務(wù)隊(duì)列中取一個(gè)微任務(wù)放到我們的任務(wù)隊(duì)列中進(jìn)行執(zhí)行,將它的回調(diào)函數(shù)進(jìn)而再次放到執(zhí)行棧中進(jìn)行執(zhí)行,當(dāng)微任務(wù)隊(duì)列為空時(shí),會(huì)在宏任務(wù)中取異步任務(wù)加到任務(wù)隊(duì)列,進(jìn)而壓入執(zhí)行棧,執(zhí)行回調(diào)函數(shù),然后繼續(xù)在該宏任務(wù)中查找同步、異步任務(wù),一次循環(huán),完成了一個(gè)事件循環(huán)(事件輪詢)
瀏覽器環(huán)境下的例子:
例子:
console.log("1"); setTimeout(() => { console.log("setTimeout"); }, 1); new Promise((res, rej) => { console.log("Promise"); res('PromiseRes') }).then(val => { console.log(val); }) console.log("2");
分析:
首先執(zhí)行棧找到第一行的同步代碼,直接扔到執(zhí)行棧中執(zhí)行,打印1,隨后為定時(shí)器setTimeout,為異步任務(wù),將代碼放到異步對(duì)列中等待執(zhí)行,隨后執(zhí)行promise中的代碼,我們要清楚promise是同步執(zhí)行,它的回調(diào)是異步執(zhí)行,所有打印Promise,將res(‘PromiseRes')放到異步對(duì)列中等待執(zhí)行,這個(gè)時(shí)候又遇到了同步代碼,打印2,當(dāng)前主線程的同步代碼全部執(zhí)行完畢,并且執(zhí)行棧中沒(méi)有要執(zhí)行的同步代碼,這個(gè)時(shí)候webApi會(huì)從異步隊(duì)列中去微任務(wù)隊(duì)列中的第一個(gè),加入到事件隊(duì)列執(zhí)行,將返回的回調(diào)函數(shù)壓入到執(zhí)行棧中執(zhí)行,打印PromiseRes,隨后微任務(wù)執(zhí)行完畢,已經(jīng)沒(méi)有微任務(wù),現(xiàn)在就需要從宏任務(wù)隊(duì)列中取宏任務(wù)定時(shí)器,加入到任務(wù)隊(duì)列中,將回調(diào)函數(shù)壓入到執(zhí)行棧中執(zhí)行,打印setTimeout。
node環(huán)境事件循環(huán)
在node中事件循環(huán)主要分為六個(gè)階段來(lái)實(shí)現(xiàn):
外部數(shù)據(jù)輸入–》輪詢階段–》檢查階段–》關(guān)閉事件回調(diào)階段–》定時(shí)器階段–》I/O回調(diào)階段–》閑置階段–》輪詢階段》…開始循環(huán)
六個(gè)階段
圖片來(lái)自網(wǎng)絡(luò)
- timers階段:用來(lái)執(zhí)行timer(setTimeout,setInterval)的回調(diào);
- I/O callbacks階段:處理一些上一輪循環(huán)中少數(shù)未執(zhí)行的I/O回調(diào)
- idle,prepare 階段:僅node內(nèi)部使用,我們用不到;
- poll階段:獲取新的I/O時(shí)間,適當(dāng)?shù)臈l件下node將阻塞在這里;
- check階段:執(zhí)行setImmediate()的回調(diào);
- close callbacks 階段:執(zhí)行socket的close時(shí)間回調(diào)
主要階段:
timer:
timers階段會(huì)執(zhí)行setTimeout和setInterval回調(diào),并且是由poll階段控制的。
同樣,在node中定時(shí)器指定的時(shí)間也不是準(zhǔn)確時(shí)間,只能是盡快執(zhí)行。
poll:
poll這一階段中,系統(tǒng)會(huì)做兩件事情:
1.回到timer階段執(zhí)行回調(diào)
2.執(zhí)行I/O回調(diào)
并且在進(jìn)入該階段時(shí)如果沒(méi)有設(shè)定了timer 的話,會(huì)發(fā)生以下兩件事情
如果 poll 隊(duì)列不為空,會(huì)遍歷回調(diào)隊(duì)列并同步執(zhí)行,直到隊(duì)列為空或者達(dá)到系統(tǒng)限制
如果 poll 隊(duì)列為空時(shí),會(huì)有兩件事發(fā)生
1、如果有 setImmediate 回調(diào)需要執(zhí)行,poll 階段會(huì)停止并且進(jìn)入到 check 階段執(zhí)行回調(diào)
2、如果沒(méi)有 setImmediate 回調(diào)需要執(zhí)行,會(huì)等待回調(diào)被加入到隊(duì)列中并立即執(zhí)行回調(diào),這里同樣會(huì)有個(gè)超時(shí)時(shí)間設(shè)置防止一直等待下去
當(dāng)然設(shè)定了 timer 的話且 poll 隊(duì)列為空,則會(huì)判斷是否有 timer 超時(shí),如果有的話會(huì)回到 timer 階段執(zhí)行回調(diào)。
check階段
setImmediate()的回調(diào)會(huì)被加入 check 隊(duì)列中,從 event loop 的階段圖可以知道,check 階段的執(zhí)行順序在 poll 階段之后,在進(jìn)入check階段執(zhí)勤poll會(huì)檢查有的話到check階段,沒(méi)有的換直接到timer階段。
(1) setTimeout 和 setImmediate
二者非常相似,區(qū)別主要在于調(diào)用時(shí)機(jī)不同。
setImmediate 設(shè)計(jì)在 poll 階段完成時(shí)執(zhí)行,即 check 階段,只有在check階段才會(huì)執(zhí)行;
setTimeout 設(shè)計(jì)在 poll 階段為空閑時(shí),且設(shè)定時(shí)間到達(dá)后執(zhí)行,但它在 timer 階段執(zhí)行,表示當(dāng)前線程沒(méi)有其他可執(zhí)行的同步任務(wù),才會(huì)在timer階段執(zhí)行定時(shí)器。
這兩個(gè)執(zhí)行的時(shí)機(jī)可前可后:
例子1:
// //異步任務(wù)中的宏任務(wù) setTimeout(() => { console.log('===setTimeout==='); },0); setImmediate(() => { console.log('===setImmediate===') })
多次重復(fù)執(zhí)行的結(jié)果會(huì)不同,有一種隨機(jī)的感覺(jué),出現(xiàn)這種情況的原因主要和setTimeout的實(shí)現(xiàn)代碼有關(guān),當(dāng)我們不傳時(shí)間參數(shù)或者設(shè)置為0的時(shí)候,nodejs會(huì)取值為1,即1ms(在瀏覽器端可能取值會(huì)更大一下,不同瀏覽器也各不相同),所以在電腦cpu性能夠強(qiáng),能夠在1ms內(nèi)執(zhí)行到timers phase的情況下,由于時(shí)間延遲不滿足回調(diào)不會(huì)被執(zhí)行,于是只能等到第二輪再執(zhí)行,這樣setInterval就會(huì)先執(zhí)行。
可能由于cpu多次執(zhí)行相同任務(wù)用時(shí)會(huì)有細(xì)微差別,而且在1ms上下浮動(dòng),才會(huì)造成上面的隨機(jī)現(xiàn)象
一般情況下setTimeout為0時(shí)候會(huì)在setImmediate之前執(zhí)行
例子2:
當(dāng)我們傳入的值大于定時(shí)器timer執(zhí)行的回調(diào)時(shí)間的時(shí)候會(huì)直接導(dǎo)致定時(shí)器在下一次事件循環(huán)中執(zhí)行
setTimeout(() => { console.log('===setTimeout==='); },10); setImmediate(() => { console.log('===setImmediate===') })
例子3:
當(dāng)我們將上述代碼放入一個(gè)i/o中就會(huì)固定先check再而timer:
const fs = require('fs'); fs.readFile("./any.js", (data) => { setTimeout(() => { console.log('===setTimeout==='); },10); setImmediate(() => { console.log('===setImmediate===') }) });
在第一輪循環(huán)中讀取文件,在回調(diào)中,會(huì)進(jìn)入check階段進(jìn)而執(zhí)行setImmediate,隨后timer階段執(zhí)行定時(shí)器。
setimmediate 與 settimeout 放入一個(gè) I/O 循環(huán)內(nèi)調(diào)用,則 setImmediate 總是被優(yōu)先調(diào)用
(2) process.nextTick
這個(gè)函數(shù)其實(shí)是獨(dú)立于 Event Loop 之外的,它有一個(gè)自己的隊(duì)列,當(dāng)每個(gè)階段完成后,如果存在 nextTick 隊(duì)列,就會(huì)清空隊(duì)列中的所有回調(diào)函數(shù),并且優(yōu)先于其他 microtask 執(zhí)行。
例子1:
setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') }) }) }) }) // nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
例子2:
const fs = require('fs'); fs.readFile("./any.js", (data) => { process.nextTick(()=>console.log('process===2')) setTimeout(() => { console.log('===setTimeout==='); },10); setImmediate(() => { console.log('===setImmediate===') }) }); process.nextTick(()=>console.log('process===1'))
練習(xí)例子
async function async1() { console.log('2') //會(huì)等待await執(zhí)行完 但是不會(huì)向下執(zhí)行 因?yàn)橄旅孑斎胛⑷蝿?wù) await async2() console.log('9') } function async2() { console.log('3') } console.log('1') setTimeout(function () { console.log('11') }, 0) setTimeout(function () { console.log('13') }, 300) setImmediate(() => console.log('12')); process.nextTick(() => console.log('7')); async1(); process.nextTick(() => console.log('8')); new Promise(function (resolve) { console.log('4') resolve(); console.log('5') }).then(function () { console.log('10') }) console.log('6')
分析:
上面的循序就是序號(hào)的順序;
首先打印1:
前面都是兩個(gè)函數(shù)聲明,所有直接打印1,這行同步代碼;
打印2:
打印完1后,都是異步代碼,加入異步任務(wù)隊(duì)列,直接到async1函數(shù)調(diào)用,在這個(gè)函數(shù)中打印2;
打印3:
async1這個(gè)函數(shù)是個(gè)async await函數(shù),所有也是一個(gè)變相的同步操縱等待async2函數(shù)執(zhí)行,async2執(zhí)行后并不會(huì)直接打印9,原因await接受的是一個(gè)promise的then操作,所以后面屬于一個(gè)promise的回調(diào)操作屬于微任務(wù),加入微任務(wù)隊(duì)列;
打印4:
process.nextTick為微任務(wù),所以會(huì)繼續(xù)執(zhí)行promise,打印4;
打印5:
resolve()的回調(diào)不會(huì)立即執(zhí)行屬于微任務(wù),加入微任務(wù)隊(duì)列,所以打印5;
打印6:
最后一個(gè)主線程的同步代碼,打印6;
打印7、8:
process.nextTick優(yōu)先級(jí)高于其他定時(shí)器,所以會(huì)直接執(zhí)行回調(diào)函數(shù)打印7、8;
打印9、10:
這個(gè)時(shí)候需要執(zhí)行微任務(wù)隊(duì)列中的微任務(wù),目前有兩個(gè)9和10,按照先后循序,先打印9后打印10;
打印11、12:
setTimeout為0秒比setImmediate執(zhí)行早,按照先后循序,先打印11后打印12;
打印13:
setTimeout為300ms的函數(shù),打印13;
例子:
async function async1() { console.log('2') //會(huì)等待await執(zhí)行完 但是不會(huì)向下執(zhí)行 因?yàn)橄旅孑斎胛⑷蝿?wù) await async2() console.log('9') } function async2() { console.log('3') } console.log('1') setTimeout(function () { console.log('11') setTimeout(() => { console.log('11-1'); },100); setImmediate(() => { console.log('11-2') }) }, 0) setTimeout(function () { console.log('13') setTimeout(() => { console.log('15'); },10); setImmediate(() => { console.log('14') }) }, 300) setImmediate(() => console.log('12')); process.nextTick(() => console.log('7')); async1(); process.nextTick(() => console.log('8')); new Promise(function (resolve) { console.log('4') resolve(); console.log('5') }).then(function () { console.log('10') }) console.log('6')
總結(jié):
到此這篇關(guān)于node事件循環(huán)中事件執(zhí)行的順序的文章就介紹到這了,更多相關(guān)node 事件執(zhí)行的順序內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nodejs中fs文件系統(tǒng)模塊的路徑動(dòng)態(tài)拼接的問(wèn)題和解決方案
在使用fs模塊操作文件時(shí),如果提供的操作路徑是以./或../開頭的相對(duì)路徑時(shí),很容易出現(xiàn)路徑動(dòng)態(tài)拼接錯(cuò)誤的問(wèn)題,所以本文給大家介紹了Nodejs中fs文件系統(tǒng)模塊的路徑動(dòng)態(tài)拼接的問(wèn)題和解決方案,需要的朋友可以參考下2024-03-03Node.js中的WebSocket底層實(shí)現(xiàn)
WebSockets是基于HTTP的雙向通信協(xié)議,允許客戶端和服務(wù)器之間實(shí)現(xiàn)實(shí)時(shí)、持久的數(shù)據(jù)交換,本文詳細(xì)介紹了使用JavaScript和Node.js創(chuàng)建WebSockets服務(wù)器和客戶端的過(guò)程,感興趣的可以了解一下2024-10-10Node.js中的http請(qǐng)求客戶端示例(request client)
本篇文章主要介紹了Node.js中的http請(qǐng)求客戶端示例(request client),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05使用Fetch API執(zhí)行GraphQL查詢和變體問(wèn)題
這篇文章主要介紹了使用Fetch API執(zhí)行GraphQL查詢和變體問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04windows 下安裝nodejs 環(huán)境變量設(shè)置
windows 下安裝nodejs 了,也安裝了npm, 但是有時(shí)候切不能直接用request(‘ws’)這一類的東西.我覺(jué)得是確實(shí)環(huán)境變量或其他設(shè)置有問(wèn)題,能否給個(gè)完整的設(shè)置方案:2017-02-02基于node實(shí)現(xiàn)websocket協(xié)議
這篇文章主要介紹了基于node實(shí)現(xiàn)websocket協(xié)議的相關(guān)資料,需要的朋友可以參考下2016-04-04Vue+Node服務(wù)器查詢Mongo數(shù)據(jù)庫(kù)及頁(yè)面數(shù)據(jù)傳遞操作實(shí)例分析
這篇文章主要介紹了Vue+Node服務(wù)器查詢Mongo數(shù)據(jù)庫(kù)及頁(yè)面數(shù)據(jù)傳遞操作,結(jié)合實(shí)例形式分析了node.js查詢MongoDB數(shù)據(jù)庫(kù)及vue前臺(tái)頁(yè)面渲染等相關(guān)操作技巧,需要的朋友可以參考下2019-12-12Node+Express搭建HTTPS服務(wù)的實(shí)現(xiàn)
最近開發(fā)需要搭建一個(gè)https的服務(wù),正好最近在用nodejs和express,本文章主要介紹了Node+Express搭建HTTPS服務(wù)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12