摘要:如果一個(gè)即時(shí)定時(shí)器是被一個(gè)正在執(zhí)行的回調(diào)排入隊(duì)列的,則該定時(shí)器直到下一次事件循環(huán)迭代才會(huì)被觸發(fā)。參數(shù)描述在事件循環(huán)的當(dāng)前回合結(jié)束時(shí)要調(diào)用的函數(shù)。事件輪詢隨后的調(diào)用,會(huì)在任何事件包括定時(shí)器之前運(yùn)行。
系列文章
Nodejs高性能原理(上) --- 異步非阻塞事件驅(qū)動(dòng)模型
Nodejs高性能原理(下) --- 事件循環(huán)詳解
終于開始我nodejs的博客生涯了,先從基本的原理講起.以前寫過(guò)一篇瀏覽器執(zhí)行機(jī)制的文章,和nodejs的相似之處還是挺多的,不熟悉可以去看看先.
Javascript執(zhí)行機(jī)制--單線程,同異步任務(wù),事件循環(huán)
寫下來(lái)之后可能還是有點(diǎn)懞,以后慢慢補(bǔ)充,也歡迎指正,特別是那篇翻譯文章后面已經(jīng)看不懂了.有人出手科普一下就好了.因?yàn)閼械脛?dòng)手做,整篇文章的圖片要么來(lái)源官網(wǎng),要么來(lái)源百度圖片.
補(bǔ)充: 當(dāng)前Nodejs版本10.3.0
2019/8/13 修改部分描述內(nèi)容
基本來(lái)自The Node.js Event Loop, Timers, and process.nextTick(),可以說(shuō)這部分我就是翻譯功能,部分翻譯太繞口會(huì)和諧一下,基本忠于原文.
當(dāng)nodejs開始運(yùn)行的時(shí)候會(huì)初始化事件循環(huán),處理所提供的輸入腳本或者放置進(jìn)REPL(Read Eval Print Loop:交互式解釋器類似 Window 系統(tǒng)的終端或 Unix/Linux shell),可能會(huì)進(jìn)行異步API調(diào)用.定時(shí)器調(diào)度,或者process.nextTick(),然后開始處理事件循環(huán)的流程.
下面來(lái)自官網(wǎng)的炫酷流程代碼示意圖(官網(wǎng)直接用符號(hào)拼湊出來(lái),這里因?yàn)榫庉嬈鲉?wèn)題衹能截圖)
注意: 每個(gè)框都被稱為事件循環(huán)的一個(gè)流程階段.
每個(gè)階段都有一個(gè)FIFO(先進(jìn)先出)執(zhí)行回調(diào)函數(shù)的隊(duì)列,然而每個(gè)階段都有其獨(dú)特之處.通常當(dāng)事件循環(huán)進(jìn)入到給定階段會(huì)執(zhí)行特定于該階段的所有操作.然后執(zhí)行該階段隊(duì)列的回調(diào)事件直到隊(duì)列耗盡或者超過(guò)最大執(zhí)行限度為止,然后事件循環(huán)就會(huì)走向下一階段,以此類推.
因?yàn)檫@些操作可能會(huì)調(diào)度更多的操作并且在poll階段中新的處理事件會(huì)加入到內(nèi)核的隊(duì)列,即處理輪詢事件時(shí)候又加入新的輪詢事件,因此,長(zhǎng)時(shí)間運(yùn)行回調(diào)事件會(huì)讓poll階段運(yùn)行時(shí)間超過(guò)定時(shí)器的閾值.
階段綜述:timers(定時(shí)器): 這階段執(zhí)行setTimeout和setInterval調(diào)度的回調(diào);
pending callbacks(等待回調(diào)): 推遲到下一次循環(huán)迭代執(zhí)行I/O回調(diào);
idle,prepare(閑置,準(zhǔn)備): 只能內(nèi)部使用;
poll(輪詢): 檢索新的I/O事件;執(zhí)行I/O相關(guān)回調(diào)(除了close callbacks以外,大多數(shù)是定時(shí)器調(diào)度,和setImmediate()),當(dāng)運(yùn)行時(shí)候適當(dāng)條件下nodejs會(huì)占用阻塞;
check(檢測(cè)): setImmediate()回調(diào)就在這執(zhí)行;
close callbacks(關(guān)閉回調(diào)): 一些關(guān)閉回調(diào),例如socket.on("close", ...),
在事件循環(huán)的每次運(yùn)行過(guò)程中,nodejs會(huì)檢測(cè)是否有任何待處理的異步I/O或者定時(shí)器,沒有的話就徹底清除關(guān)閉.
Timers(定時(shí)器)在定時(shí)器設(shè)定了一個(gè)閾值之后,被提供的回調(diào)函數(shù)實(shí)際執(zhí)行時(shí)間可能不是開發(fā)者想要它被執(zhí)行的時(shí)間,定時(shí)器回調(diào)會(huì)在指定閾值過(guò)去后盡可能早的運(yùn)行,然而操作系統(tǒng)調(diào)度或者其他回調(diào)運(yùn)行都可能會(huì)導(dǎo)致延遲.
注意: 為了防止輪詢階段持續(xù)時(shí)間太長(zhǎng),libuv 會(huì)根據(jù)操作系統(tǒng)的不同設(shè)置一個(gè)輪詢的上限。(這就是為什么上面會(huì)說(shuō)執(zhí)行該階段隊(duì)列的回調(diào)事件直到隊(duì)列耗盡或者超過(guò)最大執(zhí)行限度為止)
(下面會(huì)多帶帶詳細(xì)講解定時(shí)器的東西)
這階段會(huì)執(zhí)行一些系統(tǒng)操作回調(diào)像TCP錯(cuò)誤類型,例如當(dāng)一個(gè)TCP socket想要連接的時(shí)候接收到ECONNREFUSED,一些*nix系統(tǒng)會(huì)等待錯(cuò)誤報(bào)文,這會(huì)被排在pending callbacks 階段執(zhí)行.
poll(輪詢)這階段有兩個(gè)主要功能:
計(jì)算它應(yīng)該阻塞多長(zhǎng)的時(shí)間和進(jìn)行輪詢I/O操作;
(原文: Calculating how long it should block and poll for I/O, then,我看到有些人會(huì)翻譯成當(dāng) timers 的定時(shí)器到期后,執(zhí)行定時(shí)器(setTimeout 和 setInterval)的 callback。不知道版本不對(duì)還是我翻譯不對(duì)味)
處理poll隊(duì)列事件;
當(dāng)事件循環(huán)進(jìn)入poll階段,并且沒有timers調(diào)度,會(huì)發(fā)生其中一種情況:
如果poll隊(duì)列不為空,事件循環(huán)會(huì)迭代回調(diào)隊(duì)列同步執(zhí)行它們直到隊(duì)列耗盡或者到達(dá)系統(tǒng)限制;
如果poll隊(duì)列為空,一件或者多件情況會(huì)發(fā)生:
如果setImmediate()腳本已經(jīng)被調(diào)度,事件循環(huán)的poll階段完成然后繼續(xù)到check階段去執(zhí)行那里的調(diào)度腳本;
如果setImmediate()腳本還沒被調(diào)度,事件循環(huán)會(huì)等待回調(diào)被添加到隊(duì)列,然后立即執(zhí)行.
一旦poll隊(duì)列清空了事件循環(huán)會(huì)檢測(cè)有沒有定時(shí)器閾值是否到達(dá),如果一個(gè)或多個(gè)定時(shí)器已經(jīng)準(zhǔn)備好,事件循環(huán)會(huì)繞回到timers階段去執(zhí)行它們的定時(shí)器回調(diào)函數(shù).
check(檢測(cè))這階段允許開發(fā)者在poll階段完成之后立即執(zhí)行回調(diào)函數(shù),如果poll階段在閑置中并且腳本已經(jīng)被setImmediate()加入隊(duì)列,事件循環(huán)會(huì)跳到check階段而不是等待.
setImmediate()實(shí)際上是一個(gè)特殊的定時(shí)器,它會(huì)在事件循環(huán)的多帶帶階段運(yùn)行.通過(guò)libuv API在poll階段完成之后調(diào)度回調(diào)去執(zhí)行.
一般來(lái)說(shuō),當(dāng)代碼執(zhí)行完,事件循環(huán)最終會(huì)到達(dá)poll階段去等待即將到來(lái)的連接,請(qǐng)求等等.然而,如果一個(gè)回調(diào)函數(shù)被setImmediate()調(diào)度并且poll階段是閑置狀態(tài),它會(huì)結(jié)束并且跳到check階段而不是在等待輪詢事件.
close callbacks(關(guān)閉回調(diào))如果一個(gè)socket或handle突然被關(guān)閉(例如socket.destroy()),"close"事件會(huì)在這階段被觸發(fā),否則會(huì)通過(guò)process.nextTick()被觸發(fā).
非異步API(強(qiáng)勢(shì)插樓)事件循環(huán)階段部分已經(jīng)講完了,剩下的是定時(shí)器之間區(qū)別部分,在那之前我想在這里補(bǔ)充一下定時(shí)器知識(shí)!
Node.js 中的計(jì)時(shí)器函數(shù)實(shí)現(xiàn)使用了一個(gè)與瀏覽器類似但不同的內(nèi)部實(shí)現(xiàn),它是基于 Node.js 事件循環(huán)構(gòu)建的。
瀏覽器定時(shí)器 setTimeout(callback,delay,lang) :在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式,返回一個(gè)用于 clearTimeout() 的Timeout或窗口被關(guān)閉。
參數(shù) | 描述 | ||
---|---|---|---|
callback | 必需。要調(diào)用的函數(shù)后要執(zhí)行的 JavaScript 代碼串。 | ||
delay | 必需。在執(zhí)行代碼前需等待的毫秒數(shù), W3C標(biāo)準(zhǔn)規(guī)定時(shí)間間隔低于4ms被算為4ms,具體看瀏覽器 | ||
lang | 可選。腳本語(yǔ)言可以是:JScript | VBScript | JavaScript |
按照指定的周期(以毫秒計(jì))來(lái)調(diào)用函數(shù)或計(jì)算表達(dá)式。方法會(huì)不停地調(diào)用函數(shù),返回一個(gè)用于 clearInterval() 的Timeout或窗口被關(guān)閉。
參數(shù)請(qǐng)看上面setTimeout.
在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式,返回一個(gè)用于 clearTimeout() 的Timeout或窗口被關(guān)閉。
參數(shù) | 描述 |
---|---|
callback | 必需。要調(diào)用的函數(shù)后要執(zhí)行的 JavaScript 代碼串。 |
delay | 必需。在執(zhí)行代碼前需等待的毫秒數(shù)。當(dāng) delay 大于 2147483647 或小于 1 時(shí),delay 會(huì)被設(shè)為 1。 |
...args | 可選, 當(dāng)調(diào)用 callback 時(shí)要傳入的可選參數(shù)。 |
此外還增加一些方法timeout.ref(),timeout.unref()等,請(qǐng)自行查看.Timeout 類
setInterval(callback, delay[, ...args])按照指定的周期(以毫秒計(jì))來(lái)調(diào)用函數(shù)或計(jì)算表達(dá)式。方法會(huì)不停地調(diào)用函數(shù),返回一個(gè)用于 clearInterval() 的Timeout或窗口被關(guān)閉。
參數(shù)請(qǐng)看上面setTimeout.
預(yù)定立即執(zhí)行的 callback,它是在 I/O 事件的回調(diào)之后被觸發(fā)。 返回一個(gè)用于 clearImmediate() 的 Immediate。
當(dāng)多次調(diào)用 setImmediate() 時(shí),callback 函數(shù)會(huì)按照它們被創(chuàng)建的順序依次執(zhí)行。 每次事件循環(huán)迭代都會(huì)處理整個(gè)回調(diào)隊(duì)列。 如果一個(gè)即時(shí)定時(shí)器是被一個(gè)正在執(zhí)行的回調(diào)排入隊(duì)列的,則該定時(shí)器直到下一次事件循環(huán)迭代才會(huì)被觸發(fā)。
參數(shù) | 描述 |
---|---|
callback | 在 Node.js 事件循環(huán)的當(dāng)前回合結(jié)束時(shí)要調(diào)用的函數(shù)。 |
...args | 可選, 當(dāng)調(diào)用 callback 時(shí)要傳入的可選參數(shù)。 |
對(duì)應(yīng)的清除方法clearImmediate(),此外還增加一些方法setImmediate.ref(),setImmediate.unref()等,請(qǐng)自行查看.Immediate 類
promise寫法(題外話)可用util.promisify()提供的promises常用變體
const util = require("util"); const setTimeoutPromise = util.promisify(setTimeout), setImmediatePromise = util.promisify(setImmediate); setTimeoutPromise(40, "foobar").then(value => { // value === "foobar" (passing values is optional) // This is executed after about 40 milliseconds. }); setImmediatePromise("foobar").then(value => { // value === "foobar" (passing values is optional) // This is executed after all I/O callbacks. }); // or with async function async function timerExample() { console.log("Before I/O callbacks"); await setImmediatePromise(); console.log("After I/O callbacks"); } timerExample();process.nextTick(callback[, ...args])
將 callback 添加到next tick 隊(duì)列。 一旦當(dāng)前事件輪詢隊(duì)列的任務(wù)全部完成,在next tick隊(duì)列中的所有callbacks會(huì)被依次調(diào)用。但是不同于上面的定時(shí)器.在內(nèi)部的處理機(jī)制不同,nextTick擁有比延時(shí)更多的特性.
注意:這不是定時(shí)器,而且遞歸調(diào)用nextTick callbacks 會(huì)阻塞任何I/O操作,就像一個(gè)while(true)循環(huán)一樣
參數(shù) | 描述 |
---|---|
callback | 一旦當(dāng)前事件輪詢隊(duì)列的任務(wù)全部完成,在next tick隊(duì)列中要調(diào)用的函數(shù) |
...args | 可選, 當(dāng)調(diào)用 callback 時(shí)要傳入的可選參數(shù)。 |
console.log("start"); process.nextTick(() => { console.log("nextTick callback"); }); console.log("scheduled"); // Output: // start // scheduled // nextTick callback在對(duì)象構(gòu)造好但還沒有任何I/O發(fā)生之前,想給用戶機(jī)會(huì)來(lái)指定某些事件處理器。
function MyThing(options) { this.setupOptions(options); process.nextTick(() => { this.startDoingStuff(); }); } const thing = new MyThing(); thing.getReadyForStuff(); // thing.startDoingStuff() gets called now, not before.每次事件輪詢后,在額外的I/O執(zhí)行前,next tick隊(duì)列都會(huì)優(yōu)先執(zhí)行。
//使用場(chǎng)景 const maybeTrue = Math.random() > 0.5; maybeSync(maybeTrue, () => { foo(); }); bar(); //危險(xiǎn)寫法,因?yàn)椴磺宄oo() 或 bar() 哪個(gè)會(huì)被先調(diào)用 function maybeSync(arg, cb) { if (arg) { cb(); return; } fs.stat("file", cb); } //優(yōu)化寫法,每次事件輪詢后,在額外的I/O執(zhí)行前,next tick隊(duì)列都會(huì)優(yōu)先執(zhí)行 function definitelyAsync(arg, cb) { if (arg) { process.nextTick(cb); return; } fs.stat("file", cb); }繼續(xù)回到文章 setImmediate() vs setTimeout()
setImmediate() vs setTimeout()很相似,但是行為方式的不同取決于他們調(diào)用時(shí)機(jī).
setImmediate()被設(shè)計(jì)為在當(dāng)前poll階段完成之后執(zhí)行腳本.
setTimeout()會(huì)在消耗一段時(shí)間閾值之后調(diào)度一段腳本去運(yùn)行.
定時(shí)器被執(zhí)行時(shí)候的順序變化取決于它們被調(diào)用時(shí)候的上下文,如果都是在主模塊內(nèi)部被調(diào)用會(huì)受到進(jìn)程性能的約束(可能被本機(jī)其他應(yīng)用運(yùn)行影響);
例如,如果我們不在I/O循環(huán)運(yùn)行下面的腳本(也就是在主模塊中),兩個(gè)定時(shí)器的執(zhí)行順序是不確定的,因?yàn)樗鼈兪艿竭M(jìn)程性能的約束.
// timeout_vs_immediate.js setTimeout(function timeout () { console.log("timeout"); },0); setImmediate(function immediate () { console.log("immediate"); });
$ node timeout_vs_immediate.js
timeout
immediate$ node timeout_vs_immediate.js
immediate
timeout
可是如果你把兩個(gè)代碼放進(jìn)I/O循環(huán)內(nèi)部,immediate()回調(diào)函數(shù)總是先執(zhí)行;
// timeout_vs_immediate.js const fs = require("fs"); fs.readFile(__filename, () => { setTimeout(() => { console.log("timeout"); }, 0); setImmediate(() => { console.log("immediate"); }); });
$ node timeout_vs_immediate.js
immediate
timeout$ node timeout_vs_immediate.js
immediate
timeout
使用setImmediate()而不是setTimeout()的主要優(yōu)勢(shì)是如果在I/O循環(huán)內(nèi)部調(diào)用,setImmediate()總會(huì)在所有定時(shí)器之前執(zhí)行,與你定義多少個(gè)定時(shí)器無(wú)關(guān).
理解process.nextTick()你可能已經(jīng)注意到process.nextTick()并沒有出現(xiàn)在圖表,雖然它是異步API的一部分,那是因?yàn)閜rocess.nextTick()技術(shù)上不是事件循環(huán)部分.相反,process.nextTick()會(huì)在當(dāng)前操作完成之后被處理,不管事件循環(huán)的當(dāng)前階段如何.
回顧我們的圖表,在給定階段的任何時(shí)候你調(diào)用process.nextTick(),傳遞給process.nextTick()的回調(diào)函數(shù)都會(huì)在事件循環(huán)繼續(xù)之前被解決,這會(huì)造成一些糟糕情況因?yàn)樗试S你通過(guò)執(zhí)行遞歸process.nextTick()調(diào)用去"餓死"(starve)你的I/O,從而阻止事件循環(huán)到達(dá)poll階段.
為什么會(huì)被允許?為什么一些像這樣的內(nèi)容會(huì)被包含在Nodejs?這部分是因?yàn)樗且环N設(shè)計(jì)哲學(xué),API應(yīng)該總是異步即使它并不需要,看這段代碼片段例子
function apiCall(arg, callback) { if (typeof arg !== "string") return process.nextTick(callback, new TypeError("argument should be string")); }
這片段會(huì)檢查入?yún)?如果不正確會(huì)傳遞錯(cuò)誤到回調(diào)函數(shù),最近更新的API允許傳遞入?yún)⒌絧rocess.nextTick(),允許他在回調(diào)后取傳遞的任何參數(shù)作為回調(diào)的入?yún)?這樣就不必嵌套函數(shù)了.
這句又長(zhǎng)又繞口,附上部分原文:
The API updated fairly recently to allow passing arguments to process.nextTick() allowing it to take any arguments passed after the callback to be propagated as the arguments to the callback so you don"t have to nest functions.
我們要做的是傳遞一個(gè)錯(cuò)誤給開發(fā)者但僅僅是我們已經(jīng)允許開發(fā)者其余的代碼執(zhí)行之后.通過(guò)使用process.nextTick()我們保證apiCall()總會(huì)在開發(fā)者其余代碼執(zhí)行之后事件循環(huán)允許執(zhí)行之前運(yùn)行它的回調(diào)函數(shù),為了實(shí)現(xiàn)這一步,JS調(diào)用堆棧允許展開立即執(zhí)行所提供的回調(diào)函數(shù),允許開發(fā)者執(zhí)行遞歸調(diào)用process.nextTick()而不會(huì)達(dá)到引用錯(cuò)誤: Maximum call stack size exceeded from v8.
這句又長(zhǎng)又繞口,附上原文:
What we"re doing is passing an error back to the user but only after we have allowed the rest of the user"s code to execute. By using process.nextTick() we guarantee that apiCall() always runs its callback after the rest of the user"s code and before the event loop is allowed to proceed. To achieve this, the JS call stack is allowed to unwind then immediately execute the provided callback which allows a person to make recursive calls to process.nextTick() without reaching a RangeError: Maximum call stack size exceeded from v8.
這種哲學(xué)會(huì)導(dǎo)致一些潛在的有問(wèn)題的情況,看看這段片段例子
let bar; // this has an asynchronous signature, but calls callback synchronously function someAsyncApiCall(callback) { callback(); } // the callback is called before `someAsyncApiCall` completes. someAsyncApiCall(() => { // since someAsyncApiCall has completed, bar hasn"t been assigned any value console.log("bar", bar); // undefined }); bar = 1;
開發(fā)者定義someAsyncApiCall()有一個(gè)異步簽名(signature??),實(shí)際上卻是同步操作,當(dāng)它調(diào)用時(shí)候提供給someAsyncApiCall()的回調(diào)函數(shù)會(huì)在事件循環(huán)的相同階段被調(diào)用因?yàn)閟omeAsyncApiCall()實(shí)際上并沒有做任何異步事情,結(jié)果回調(diào)函數(shù)試著去引用bar即使它可能還沒在作用域里,因?yàn)榇a不可能運(yùn)行完成.
但是如果把它放進(jìn)process.nextTick(),代碼依舊有能力跑完,允許所有變量,函數(shù)等等在回調(diào)函數(shù)被調(diào)用之前優(yōu)先初始化完,它具有不讓事件循環(huán)繼續(xù)的優(yōu)點(diǎn),在允許事件循環(huán)繼續(xù)之前,提醒用戶注意錯(cuò)誤可能是有用的。
這句又長(zhǎng)又繞口,附上原文:
The user defines someAsyncApiCall() to have an asynchronous signature, but it actually operates synchronously. When it is called, the callback provided to someAsyncApiCall() is called in the same phase of the event loop because someAsyncApiCall() doesn"t actually do anything asynchronously. As a result, the callback tries to reference bar even though it may not have that variable in scope yet, because the script has not been able to run to completion.By placing the callback in a process.nextTick(), the script still has the ability to run to completion, allowing all the variables, functions, etc., to be initialized prior to the callback being called. It also has the advantage of not allowing the event loop to continue. It may be useful for the user to be alerted to an error before the event loop is allowed to continue. Here is the previous example using process.nextTick():
這是上面使用process.nextTick()的例子
let bar; function someAsyncApiCall(callback) { process.nextTick(callback); } someAsyncApiCall(() => { console.log("bar", bar); // 1 }); bar = 1;
這是另一個(gè)現(xiàn)實(shí)世界的例子:
const server = net.createServer(() => {}).listen(8080); server.on("listening", () => {});
(這句又長(zhǎng)又繞口,不想翻了:)
When only a port is passed, the port is bound immediately. So, the "listening" callback could be called immediately. The problem is that the .on("listening") callback will not have been set by that time.
想要避開這問(wèn)題,"listening"事件會(huì)加入nextTick()隊(duì)列以容許腳本運(yùn)行完,這允許開發(fā)者設(shè)置任何他們想要的任何事件處理器.
process.nextTick() vs setImmediate()就用戶而言,我們有兩個(gè)類似的調(diào)用,不過(guò)他們的名字令人困惑.
process.nextTick() 在同一階段立刻觸發(fā)(原文fires: 點(diǎn)燃;解雇;開除;使發(fā)光;燒制;激動(dòng);放槍???)
setImmediate() 在事件循環(huán)的后續(xù)迭代或“tick”中觸發(fā)(原文fires)
本質(zhì)上,名字應(yīng)該調(diào)換,process.nextTick()比setImmediate()更加容易觸發(fā),但這是一種不可變得的過(guò)去的產(chǎn)物,這種轉(zhuǎn)換會(huì)在npm中破壞大量的包,每天都有很多新包被添加,意味著我們每等待一天就有更多潛在的破壞發(fā)生,即使它們多困惑也不能更改它們的名字.
我們建議開發(fā)者們?cè)谌魏吻闆r使用setImmediate()因?yàn)樗菀淄瞥?reason about??)(它會(huì)讓代碼兼容更廣泛的環(huán)境變量,像browser JS)
為什么使用process.nextTick()?(翻譯文章最后內(nèi)容)兩個(gè)原因:
1, 允許開發(fā)者們處理錯(cuò)誤,清除任何不需要的資源,或者嘗試在事件循環(huán)繼續(xù)之前再次發(fā)起請(qǐng)求.
2, 在需要的時(shí)候允許調(diào)用棧釋放(unwound??)之后但事件循環(huán)繼續(xù)之前運(yùn)行一個(gè)回調(diào)函數(shù).
一個(gè)符合開發(fā)者們期望的簡(jiǎn)單例子
const server = net.createServer(); server.on("connection", (conn) => { }); server.listen(8080); server.on("listening", () => { });
假設(shè)listen()在事件循環(huán)開始的時(shí)候運(yùn)行,但是監(jiān)聽回調(diào)被放置在setImmediate()。除非傳遞主機(jī)名立即綁定端口,想讓事件循環(huán)繼續(xù)進(jìn)行必須進(jìn)入poll階段,意味著有機(jī)會(huì)(a non-zero chance??)已經(jīng)接收到一個(gè)連接,允許在監(jiān)聽事件之前觸發(fā)連接事件。
(有段名詞不懂怎么翻譯:)
which means there is a non-zero chance that a connection could have been received allowing the connection event to be fired before the listening event
另一個(gè)例子是運(yùn)行構(gòu)造函數(shù),從EventEmitter繼承并且想要在構(gòu)造函數(shù)內(nèi)部調(diào)用一個(gè)事件。
const EventEmitter = require("events"); const util = require("util"); function MyEmitter() { EventEmitter.call(this); this.emit("event"); } util.inherits(MyEmitter, EventEmitter); const myEmitter = new MyEmitter(); myEmitter.on("event", () => { console.log("an event occurred!"); });
我們不能在構(gòu)造函數(shù)立刻發(fā)出事件是因?yàn)槟_本可能還沒處理到開發(fā)者設(shè)置觸發(fā)事件回調(diào)函數(shù)的位置,所以在構(gòu)造函數(shù)內(nèi)部本身你能使用process.nextTick()設(shè)置觸發(fā)事件回調(diào)函數(shù)以在構(gòu)造函數(shù)已經(jīng)完成之后提供期望結(jié)果。
const EventEmitter = require("events"); const util = require("util"); function MyEmitter() { EventEmitter.call(this); // use nextTick to emit the event once a handler is assigned process.nextTick(() => { this.emit("event"); }); } util.inherits(MyEmitter, EventEmitter); const myEmitter = new MyEmitter(); myEmitter.on("event", () => { console.log("an event occurred!"); });輸出例子
你們?cè)囋嚳催@個(gè)輸出順序符不符合你們預(yù)期
const fs = require("fs"); console.log("start"); setTimeout(function timeout() { console.log("模塊外部timeout"); }, 1000); setImmediate(function immediate() { console.log("模塊外部immediate"); }); process.nextTick(() => { console.log("模塊外部nextTick callback"); }); fs.readFile(__filename, () => { setTimeout(function timeout() { console.log("I/O內(nèi)部timeout"); }, 0); setImmediate(function immediate() { console.log("I/O內(nèi)部immediate"); }); process.nextTick(() => { console.log("I/O內(nèi)部nextTick callback"); }); }); console.log("end"); // start // end // 模塊外部nextTick callback // 模塊外部immediate // I/O內(nèi)部nextTick callback // I/O內(nèi)部immediate // I/O內(nèi)部timeout // 模塊外部timeoutNodejs劣勢(shì)
總的來(lái)說(shuō)單線程的鍋.
1, 異常拋出終止
我們都知道Javascript是一門單線程語(yǔ)言,在發(fā)生各種錯(cuò)誤之后,JavaScript引擎通常會(huì)停止,并拋出一個(gè)錯(cuò)誤.
Nodejs具體錯(cuò)誤直接看Error (錯(cuò)誤).
暫時(shí)還沒研究到,但是肯定可以通過(guò)一些方法解決的,后補(bǔ).
2, 不適合CPU密集型
盡管我們上面已經(jīng)提出了事件驅(qū)動(dòng)異步IO非阻塞模型的各種優(yōu)點(diǎn),但是里面有個(gè)關(guān)鍵詞叫"I/O",如果是非I/O的處理例如CPU計(jì)算還是沒改進(jìn)的,如果有長(zhǎng)時(shí)間運(yùn)行的計(jì)算,將會(huì)導(dǎo)致CPU時(shí)間片不能釋放,使得后續(xù)I/O無(wú)法發(fā)起.
可以通過(guò)把密集運(yùn)算拆分成多個(gè)小任務(wù),減輕CPU壓力.
3, 不能用到CPU的多核
現(xiàn)在的服務(wù)器操作系統(tǒng)基本都是支持多CPU/核了,單線程言語(yǔ)注定只能占用一個(gè)資源,不能充分利用.
解決單線程痛點(diǎn)方案
可以新開進(jìn)程去玩,還沒研究到不說(shuō).
process - 進(jìn)程
Node.js 中文網(wǎng) API
The Node.js Event Loop, Timers, and process.nextTick()
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/106704.html
摘要:使用了一個(gè)事件驅(qū)動(dòng)非阻塞式的模型,使其輕量又高效。的包管理器,是全球最大的開源庫(kù)生態(tài)系統(tǒng)。按照這個(gè)定義,之前所述的阻塞,非阻塞,多路復(fù)用信號(hào)驅(qū)動(dòng)都屬于同步。 系列文章 Nodejs高性能原理(上) --- 異步非阻塞事件驅(qū)動(dòng)模型Nodejs高性能原理(下) --- 事件循環(huán)詳解 前言 終于開始我nodejs的博客生涯了,先從基本的原理講起.以前寫過(guò)一篇瀏覽器執(zhí)行機(jī)制的文章,和nodej...
摘要:整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí)原文協(xié)作規(guī)范中文技術(shù)文檔協(xié)作規(guī)范阮一峰編程風(fēng)格凹凸實(shí)驗(yàn)室前端代碼規(guī)范風(fēng)格指南這一次,徹底弄懂執(zhí)行機(jī)制一次弄懂徹底解決此類面試問(wèn)題瀏覽器與的事件循環(huán)有何區(qū)別筆試題事件循環(huán)機(jī)制異步編程理解的異步 better-learning 整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí) 原文:https://www.ahwgs.cn/youxiuwenzhan...
摘要:開始執(zhí)行文件,同步代碼執(zhí)行完畢后,進(jìn)入事件循環(huán)。時(shí)間未到的時(shí)候,如果有事件返回,就執(zhí)行該事件注冊(cè)的回調(diào)函數(shù)。對(duì)于多次執(zhí)行輸出結(jié)果不同,需要了解事件循環(huán)的基礎(chǔ)問(wèn)題。 1. 說(shuō)明 nodejs是單線程執(zhí)行的,同時(shí)它又是基于事件驅(qū)動(dòng)的非阻塞IO編程模型。這就使得我們不用等待異步操作結(jié)果返回,就可以繼續(xù)往下執(zhí)行代碼。當(dāng)異步事件觸發(fā)之后,就會(huì)通知主線程,主線程執(zhí)行相應(yīng)事件的回調(diào)。 本篇文章講解n...
摘要:概述本篇主要介紹的運(yùn)行機(jī)制單線程事件循環(huán)結(jié)論先在中利用運(yùn)行至完成和非阻塞完成單線程下異步任務(wù)的處理就是先處理主模塊主線程上的同步任務(wù)再處理異步任務(wù)異步任務(wù)使用事件循環(huán)機(jī)制完成調(diào)度涉及的內(nèi)容有單線程事件循環(huán)同步執(zhí)行異步執(zhí)行定時(shí)器的事件循環(huán)開始 1.概述 本篇主要介紹JavaScript的運(yùn)行機(jī)制:單線程事件循環(huán)(Event Loop). 結(jié)論先: 在JavaScript中, 利用運(yùn)行至...
摘要:的單線程,與它的用途有關(guān)。特點(diǎn)的顯著特點(diǎn)異步機(jī)制事件驅(qū)動(dòng)。隊(duì)列的讀取輪詢線程,事件的消費(fèi)者,的主角。它將不同的任務(wù)分配給不同的線程,形成一個(gè)事件循環(huán),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給引擎。 這兩天跟同事同事討論遇到的一個(gè)問(wèn)題,js中的event loop,引出了chrome與node中運(yùn)行具有setTimeout和Promise的程序時(shí)候執(zhí)行結(jié)果不一樣的問(wèn)題,從而引出了Nodejs的...
閱讀 1675·2021-11-16 11:44
閱讀 2409·2021-10-11 11:07
閱讀 4075·2021-10-09 09:41
閱讀 679·2021-09-22 15:52
閱讀 3201·2021-09-09 09:33
閱讀 2717·2019-08-30 15:55
閱讀 2295·2019-08-30 15:55
閱讀 847·2019-08-30 15:55