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)階段–》閑置階段–》輪詢階段》…開(kāi)始循環(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(officegen)+vue(axios)在客戶端導(dǎo)出word文檔的方法
這篇文章主要介紹了nodejs(officegen)+vue(axios)在客戶端導(dǎo)出word文檔的方法,需要的朋友可以參考下2018-07-07
使用Node實(shí)現(xiàn)Git倉(cāng)庫(kù)批量克隆的操作步驟
分享一個(gè)node腳本,通過(guò)調(diào)用gitee的OpenApi獲取自己的代碼倉(cāng)庫(kù)列表,達(dá)到批量克隆項(xiàng)目的效果,文中通過(guò)代碼示例和圖文講解的非常詳細(xì),感興趣的小伙伴可以參考閱讀一下2024-04-04
node.js中優(yōu)雅的使用Socket.IO模塊的方法
Socket.IO是一個(gè)WebSocket庫(kù),包括了客戶端的js和服務(wù)器端的node.js,它的目標(biāo)是構(gòu)建可以在不同瀏覽器和移動(dòng)設(shè)備上使用的實(shí)時(shí)應(yīng)用,這篇文章主要介紹了node.js中優(yōu)雅的使用Socket.IO模塊,需要的朋友可以參考下2022-12-12
修改Nodejs內(nèi)置的npm默認(rèn)配置路徑方法
今天小編就為大家分享一篇修改Nodejs內(nèi)置的npm默認(rèn)配置路徑方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Node.js基礎(chǔ)入門之回調(diào)函數(shù)及異步與同步詳解
Node.js是一個(gè)基于Chrome?V8引擎的JavaScript運(yùn)行時(shí)。類似于Java中的JRE,.Net中的CLR。本文將詳細(xì)為大家介紹Node.js中的回調(diào)函數(shù)及異步與同步,感興趣的可以了解一下2022-03-03
windows實(shí)現(xiàn)npm和cnpm安裝步驟
這篇文章主要介紹了windows實(shí)現(xiàn)npm和cnpm安裝步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
Node.js 中 cookie-parser 依賴安裝使用詳解
文章介紹了如何在Node.js中使用cookie-parser中間件來(lái)解析、設(shè)置、簽名和清除HTTP請(qǐng)求中的Cookie,感興趣的朋友一起看看吧2025-02-02
nodejs中操作mysql數(shù)據(jù)庫(kù)示例
這篇文章主要介紹了nodejs中操作mysql數(shù)據(jù)庫(kù)示例,本文演示了如何在NodeJS中創(chuàng)建創(chuàng)建mysql連接、mysql數(shù)據(jù)庫(kù)、插入數(shù)據(jù)、查詢數(shù)據(jù)等功能,需要的朋友可以參考下2014-12-12

