這篇文章主要介紹nodejs中事件和事件循環(huán)的示例分析,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!
歙縣網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。成都創(chuàng)新互聯(lián)從2013年開始到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)。
雖然nodejs是單線程的,但是nodejs可以將操作委托給系統(tǒng)內(nèi)核,系統(tǒng)內(nèi)核在后臺處理這些任務(wù),當(dāng)任務(wù)完成之后,通知nodejs,從而觸發(fā)nodejs中的callback方法。
這些callback會被加入輪循隊(duì)列中,最終被執(zhí)行。
通過這樣的event loop設(shè)計,nodejs最終可以實(shí)現(xiàn)非阻塞的IO?!鞠嚓P(guān)推薦:《nodejs 教程》】
nodejs中的event loop被分成了一個個的phase,下圖列出了各個phase的執(zhí)行順序:
每個phase都會維護(hù)一個callback queue,這是一個FIFO的隊(duì)列。
當(dāng)進(jìn)入一個phase之后,首先會去執(zhí)行該phase的任務(wù),然后去執(zhí)行屬于該phase的callback任務(wù)。
當(dāng)這個callback隊(duì)列中的任務(wù)全部都被執(zhí)行完畢或達(dá)到了最大的callback執(zhí)行次數(shù)之后,就會進(jìn)入下一個phase。
注意, windows和linux的具體實(shí)現(xiàn)有稍許不同,這里我們只關(guān)注最重要的幾個phase。
問題:phase的執(zhí)行過程中,為什么要限制最大的callback執(zhí)行次數(shù)呢?
回答:在極端情況下,某個phase可能會需要執(zhí)行大量的callback,如果執(zhí)行這些callback花費(fèi)了太多的時間,那么將會阻塞nodejs的運(yùn)行,所以我們設(shè)置callback執(zhí)行的次數(shù)限制,以避免nodejs的長時間block。
上面的圖中,我們列出了6個phase,接下來我們將會一一的進(jìn)行解釋。
timers
timers的中文意思是定時器,也就是說在給定的時間或者時間間隔去執(zhí)行某個callback函數(shù)。
通常的timers函數(shù)有這樣兩種:setTimeout和setInterval。
一般來說這些callback函數(shù)會在到期之后盡可能的執(zhí)行,但是會受到其他callback執(zhí)行的影響。 我們來看一個例子:
const fs = require('fs');function someAsyncOperation(callback) { // Assume this takes 95ms to complete fs.readFile('/path/to/file', callback);}const timeoutScheduled = Date.now();setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`);}, 100);// do someAsyncOperation which takes 95 ms to completesomeAsyncOperation(() => { const startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { // do nothing }});
上面的例子中,我們調(diào)用了someAsyncOperation,這個函數(shù)首先回去執(zhí)行readFile方法,假設(shè)這個方法耗時95ms。接著執(zhí)行readFile的callback函數(shù),這個callback會執(zhí)行10ms。最后才回去執(zhí)行setTimeout中的callback。
所以上面的例子中,雖然setTimeout指定要在100ms之后運(yùn)行,但是實(shí)際上還要等待95 + 10 = 105 ms之后才會真正的執(zhí)行。
pending callbacks
這個phase將會執(zhí)行一些系統(tǒng)的callback操作,比如在做TCP連接的時候,TCP socket接收到了ECONNREFUSED信號,在某些liunx操作系統(tǒng)中將會上報這個錯誤,那么這個系統(tǒng)的callback將會放到pending callbacks中運(yùn)行。
或者是需要在下一個event loop中執(zhí)行的I/O callback操作。
idle, prepare
idle, prepare是內(nèi)部使用的phase,這里就不過多介紹。
poll輪詢
poll將會檢測新的I/O事件,并執(zhí)行與I / O相關(guān)的回調(diào),注意這里的回調(diào)指的是除了關(guān)閉callback,timers,和setImmediate之外的幾乎所有的callback事件。
poll主要處理兩件事情:輪詢I/O,并且計算block的時間,然后處理poll queue中的事件。
如果poll queue非空的話,event loop將會遍歷queue中的callback,然后一個一個的同步執(zhí)行,知道queue消費(fèi)完畢,或者達(dá)到了callback數(shù)量的限制。
因?yàn)閝ueue中的callback是一個一個同步執(zhí)行的,所以可能會出現(xiàn)阻塞的情況。
如果poll queue空了,如果代碼中調(diào)用了setImmediate,那么將會立馬跳到下一個check phase,然后執(zhí)行setImmediate中的callback。 如果沒有調(diào)用setImmediate,那么會繼續(xù)等待新來的callback被加入到queue中,并執(zhí)行。
check
主要來執(zhí)行setImmediate的callback。
setImmediate可以看做是一個運(yùn)行在單獨(dú)phase中的獨(dú)特的timer,底層使用的libuv API來規(guī)劃callbacks。
一般來說,如果在poll phase中有callback是以setImmediate的方式調(diào)用的話,會在poll queue為空的情況下,立馬結(jié)束poll phase,進(jìn)入check phase來執(zhí)行對應(yīng)的callback方法。
close callbacks
最后一個phase是處理close事件中的callbacks。 比如一個socket突然被關(guān)閉,那么將會觸發(fā)一個close事件,并調(diào)用相關(guān)的callback。
setTimeout和setImmediate有什么不同呢?
從上圖的phase階段可以看出,setTimeout中的callback是在timer phase中執(zhí)行的,而setImmediate是在check階段執(zhí)行的。
從語義上講,setTimeout指的是,在給定的時間之后運(yùn)行某個callback。而setImmediate是在執(zhí)行完當(dāng)前l(fā)oop中的 I/O操作之后,立馬執(zhí)行。
那么這兩個方法的執(zhí)行順序上有什么區(qū)別呢?
下面我們舉兩個例子,第一個例子中兩個方法都是在主模塊中運(yùn)行:
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
這樣運(yùn)行兩個方法的執(zhí)行順序是不確定,因?yàn)榭赡苁艿狡渌麍?zhí)行程序的影響。
第二個例子是在I/O模塊中運(yùn)行這兩個方法:
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
你會發(fā)現(xiàn),在I/O模塊中,setImmediate一定會在setTimeout之前執(zhí)行。
兩者的共同點(diǎn)
setTimeout和setImmediate兩者都有一個返回值,我們可以通過這個返回值,來對timer進(jìn)行clear操作:
const timeoutObj = setTimeout(() => { console.log('timeout beyond time'); }, 1500); const immediateObj = setImmediate(() => { console.log('immediately executing immediate'); }); const intervalObj = setInterval(() => { console.log('interviewing the interval'); }, 500); clearTimeout(timeoutObj); clearImmediate(immediateObj); clearInterval(intervalObj);
clear操作也可以clear intervalObj。
unref 和 ref
setTimeout和setInterval返回的對象都是Timeout對象。
如果這個timeout對象是最后要執(zhí)行的timeout對象,那么可以使用unref方法來取消其執(zhí)行,取消執(zhí)行完畢,可以使用ref來恢復(fù)它的執(zhí)行。
const timerObj = setTimeout(() => { console.log('will i run?'); }); timerObj.unref(); setImmediate(() => { timerObj.ref(); });
注意,如果有多個timeout對象,只有最后一個timeout對象的unref方法才會生效。
process.nextTick也是一種異步API,但是它和timer是不同的。
如果我們在一個phase中調(diào)用process.nextTick,那么nextTick中的callback會在這個phase完成,進(jìn)入event loop的下一個phase之前完成。
這樣做就會有一個問題,如果我們在process.nextTick中進(jìn)行遞歸調(diào)用的話,這個phase將會被阻塞,影響event loop的正常執(zhí)行。
那么,為什么我們還會有process.nextTick呢?
考慮下面的一個例子:
let bar; function someAsyncApiCall(callback) { callback(); } someAsyncApiCall(() => { console.log('bar', bar); // undefined }); bar = 1;
上面的例子中,我們定義了一個someAsyncApiCall方法,里面執(zhí)行了傳入的callback函數(shù)。
這個callback函數(shù)想要輸出bar的值,但是bar的值是在someAsyncApiCall方法之后被賦值的。
這個例子最終會導(dǎo)致輸出的bar值是undefined。
我們的本意是想讓用戶程序執(zhí)行完畢之后,再調(diào)用callback,那么我們可以使用process.nextTick來對上面的例子進(jìn)行改寫:
let bar; function someAsyncApiCall(callback) { process.nextTick(callback); } someAsyncApiCall(() => { console.log('bar', bar); // 1 }); bar = 1;
我們再看一個實(shí)際中使用的例子:
const server = net.createServer(() => {}).listen(8080); server.on('listening', () => {});
上面的例子是最簡單的nodejs創(chuàng)建web服務(wù)。
上面的例子有什么問題呢?listen(8000) 方法將會立馬綁定8000端口。但是這個時候,server的listening事件綁定代碼還沒有執(zhí)行。
這里實(shí)際上就用到了process.nextTick技術(shù),從而不管我們在什么地方綁定listening事件,都可以監(jiān)聽到listen事件。
process.nextTick 和 setImmediate 的區(qū)別
process.nextTick 是立馬在當(dāng)前phase執(zhí)行callback,而setImmediate是在check階段執(zhí)行callback。
所以process.nextTick要比setImmediate的執(zhí)行順序優(yōu)先。
實(shí)際上,process.nextTick和setImmediate的語義應(yīng)該進(jìn)行互換。因?yàn)閜rocess.nextTick表示的才是immediate,而setImmediate表示的是next tick。
以上是“nodejs中事件和事件循環(huán)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
文章題目:nodejs中事件和事件循環(huán)的示例分析
網(wǎng)頁地址:http://www.ekvhdxd.cn/article16/jcgddg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、移動網(wǎng)站建設(shè)、搜索引擎優(yōu)化、品牌網(wǎng)站制作、網(wǎng)站導(dǎo)航、微信公眾號
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)