:事件循環(huán))
一、什么是事件循環(huán)事件循環(huán)是 Node.js 處理非阻塞 I/O 操作的核心機(jī)制使得單線程能夠高效處理多個(gè)并發(fā)請(qǐng)求。Node.js 是基于單線程的 JavaScript 運(yùn)行時(shí)利用事件循環(huán)來(lái)處理異步操作如文件讀取、網(wǎng)絡(luò)請(qǐng)求和數(shù)據(jù)庫(kù)查詢。事件循環(huán)使得 Node.js 能夠非阻塞地運(yùn)行代碼、處理多個(gè)連接、以及執(zhí)行異步 I/O 操作。事件循環(huán)使得 Node.js 能夠處理大量并發(fā)的 I/O 操作而不會(huì)導(dǎo)致線程阻塞這是 Node.js 高效處理并發(fā)請(qǐng)求的關(guān)鍵。二、事件循環(huán)的階段事件循環(huán)分為多個(gè)階段每個(gè)階段處理特定的任務(wù)。關(guān)鍵階段如下┌───────────────────────────┐ ┌─│ Timers │ ← 執(zhí)行 setTimeout() 和 setInterval() 的回調(diào) │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ I/O Callbacks │ ← 處理一些延遲的 I/O 回調(diào) │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ Idle, prepare │ ← 內(nèi)部使用不常見(jiàn) │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ Poll │ ← 檢索新的 I/O 事件執(zhí)行與 I/O 相關(guān)的回調(diào) │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ Check │ ← 執(zhí)行 setImmediate() 回調(diào) │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ Close Callbacks │ ← 處理關(guān)閉的回調(diào)如 socket.on(close, ...) └───────────────────────────┘各階段說(shuō)明階段說(shuō)明Timers執(zhí)行setTimeout()和setInterval()的到期回調(diào)I/O Callbacks處理一些延遲的 I/O 回調(diào)如某些系統(tǒng)錯(cuò)誤回調(diào)Idle, prepareNode.js 內(nèi)部使用開發(fā)者一般不需要關(guān)心Poll檢索新的 I/O 事件執(zhí)行與 I/O 相關(guān)的回調(diào)核心階段Check執(zhí)行setImmediate()回調(diào)Close Callbacks處理關(guān)閉的回調(diào)如socket.on(close, ...)三、事件循環(huán)的流程任務(wù)進(jìn)入事件循環(huán)隊(duì)列事件循環(huán)按照階段順序進(jìn)行處理每個(gè)階段有自己的回調(diào)隊(duì)列事件循環(huán)會(huì)在 poll 階段等待新的事件到達(dá)如果沒(méi)有事件會(huì)檢查其他階段的回調(diào)如果setImmediate()和setTimeout()都存在setImmediate()在 check 階段先執(zhí)行而setTimeout()在 timers 階段執(zhí)行示例setTimeout(() { console.log(Timeout callback); }, 0); setImmediate(() { console.log(Immediate callback); }); console.log(Main thread execution);輸出順序Main thread execution 先打印。 setImmediate() 和 setTimeout() 的執(zhí)行順序取決于當(dāng)前事件循環(huán)的狀態(tài)一般 setImmediate() 會(huì)先執(zhí)行。四、宏任務(wù)與微任務(wù)宏任務(wù)MacroTasksetTimeout、setIntervalsetImmediateI/O 操作等微任務(wù)MicroTaskprocess.nextTickPromise.then執(zhí)行順序微任務(wù)優(yōu)先級(jí)高于宏任務(wù)會(huì)在當(dāng)前階段的回調(diào)結(jié)束后立即執(zhí)行。setTimeout(() { console.log(Timeout callback); }, 0); Promise.resolve().then(() { console.log(Promise callback); }); console.log(Main thread execution);執(zhí)行輸出結(jié)果Main thread execution Promise callback Timeout callback五、process.nextTick()process.nextTick()會(huì)在當(dāng)前操作結(jié)束后、下一個(gè)階段開始前執(zhí)行微任務(wù)優(yōu)先級(jí)高于 Promise。process.nextTick(() { console.log(Next tick callback); }); console.log(Main thread execution);輸出Main thread execution Next tick callback執(zhí)行優(yōu)先級(jí)對(duì)比setTimeout(() console.log(setTimeout), 0); setImmediate(() console.log(setImmediate)); Promise.resolve().then(() console.log(Promise)); process.nextTick(() console.log(nextTick)); // 輸出順序 // nextTick // Promise // setTimeout 或 setImmediate取決于事件循環(huán)狀態(tài)六、事件驅(qū)動(dòng)程序在 Node.js 中事件驅(qū)動(dòng)編程主要通過(guò)EventEmitter類來(lái)實(shí)現(xiàn)。EventEmitter是一個(gè)內(nèi)置類位于events模塊中通過(guò)繼承EventEmitter你可以創(chuàng)建自己的事件發(fā)射器并注冊(cè)和觸發(fā)事件。通過(guò)這種機(jī)制Node.js 可以高效地處理異步任務(wù)即使在單線程的環(huán)境下也能實(shí)現(xiàn)并發(fā)處理。整個(gè)事件驅(qū)動(dòng)的流程非常簡(jiǎn)潔有點(diǎn)類似于觀察者模式事件相當(dāng)于一個(gè)主題Subject而所有注冊(cè)到這個(gè)事件上的處理函數(shù)相當(dāng)于觀察者Observer?;靖拍罡拍钫f(shuō)明事件在程序中發(fā)生的動(dòng)作或狀態(tài)改變例如一個(gè)文件讀取完成或一個(gè) HTTP 請(qǐng)求到達(dá)事件觸發(fā)器EventEmitter是 Node.js 的內(nèi)置模塊用來(lái)發(fā)出和監(jiān)聽事件事件處理器與事件關(guān)聯(lián)的回調(diào)函數(shù)事件發(fā)生時(shí)被調(diào)用事件驅(qū)動(dòng)的流程注冊(cè)事件在程序中通過(guò)EventEmitter實(shí)例注冊(cè)事件和對(duì)應(yīng)的處理器觸發(fā)事件當(dāng)指定的事件發(fā)生時(shí)EventEmitter會(huì)觸發(fā)該事件處理事件事件循環(huán)會(huì)調(diào)度相應(yīng)的回調(diào)函數(shù)來(lái)執(zhí)行任務(wù)使用 EventEmitterNode.js 有多個(gè)內(nèi)置的事件我們可以通過(guò)引入events模塊并通過(guò)實(shí)例化EventEmitter類來(lái)綁定和監(jiān)聽事件// 引入 events 模塊 var events require(events); // 創(chuàng)建 eventEmitter 對(duì)象 var eventEmitter new events.EventEmitter();綁定事件處理程序// 綁定事件及事件的處理程序 eventEmitter.on(eventName, eventHandler);觸發(fā)事件// 觸發(fā)事件 eventEmitter.emit(eventName);實(shí)例 1創(chuàng)建hello.js文件代碼如下const EventEmitter require(events); const myEmitter new EventEmitter(); // 注冊(cè)事件處理器 myEmitter.on(greet, () { console.log(Hello, world!); }); // 觸發(fā)事件 myEmitter.emit(greet);輸出Hello, world!實(shí)例 2創(chuàng)建main.js文件代碼如下// 引入 events 模塊 var events require(events); // 創(chuàng)建 eventEmitter 對(duì)象 var eventEmitter new events.EventEmitter(); // 創(chuàng)建事件處理程序 var connectHandler function connected() { console.log(連接成功。); // 觸發(fā) data_received 事件 eventEmitter.emit(data_received); } // 綁定 connection 事件處理程序 eventEmitter.on(connection, connectHandler); // 使用匿名函數(shù)綁定 data_received 事件 eventEmitter.on(data_received, function() { console.log(數(shù)據(jù)接收成功。); }); // 觸發(fā) connection 事件 eventEmitter.emit(connection); console.log(程序執(zhí)行完畢。);執(zhí)行以上代碼$ node main.js 連接成功。 數(shù)據(jù)接收成功。 程序執(zhí)行完畢。七、Node 應(yīng)用程序是如何工作的在 Node 應(yīng)用程序中執(zhí)行異步操作的函數(shù)將回調(diào)函數(shù)作為最后一個(gè)參數(shù)回調(diào)函數(shù)接收錯(cuò)誤對(duì)象作為第一個(gè)參數(shù)。創(chuàng)建一個(gè)input.txt文件內(nèi)容如下地址www.chenbyte.cn創(chuàng)建main.js文件代碼如下var fs require(fs); fs.readFile(input.txt, function(err, data) { if (err) { console.log(err.stack); return; } console.log(data.toString()); }); console.log(程序執(zhí)行完畢);以上程序中fs.readFile()是異步函數(shù)用于讀取文件。如果在讀取文件過(guò)程中發(fā)生錯(cuò)誤錯(cuò)誤err對(duì)象就會(huì)輸出錯(cuò)誤信息。如果沒(méi)發(fā)生錯(cuò)誤readFile跳過(guò)err對(duì)象的輸出文件內(nèi)容就通過(guò)回調(diào)函數(shù)輸出。執(zhí)行以上代碼執(zhí)行結(jié)果如下程序執(zhí)行完畢 地址www.chenbyte.cn接下來(lái)我們刪除input.txt文件執(zhí)行結(jié)果如下所示程序執(zhí)行完畢 Error: ENOENT, open input.txt因?yàn)槲募nput.txt不存在所以輸出了錯(cuò)誤信息。八、本章小結(jié)知識(shí)點(diǎn)說(shuō)明事件循環(huán)Node.js 處理非阻塞 I/O 的核心機(jī)制六個(gè)階段Timers、I/O Callbacks、Idle/prepare、Poll、Check、Close Callbacks宏任務(wù)setTimeout、setInterval、setImmediate、I/O 操作微任務(wù)process.nextTick、Promise.then微任務(wù)優(yōu)先執(zhí)行process.nextTick優(yōu)先級(jí)最高在當(dāng)前操作結(jié)束后立即執(zhí)行EventEmitter事件驅(qū)動(dòng)編程的核心類用于注冊(cè)和觸發(fā)事件觀察者模式事件相當(dāng)于主題處理函數(shù)相當(dāng)于觀察者提示理解事件循環(huán)是掌握 Node.js 異步編程的關(guān)鍵。事件循環(huán)使得 Node.js 能夠在單線程環(huán)境下高效處理大量并發(fā) I/O 操作。記住微任務(wù)process.nextTick和Promise.then會(huì)在當(dāng)前階段結(jié)束后立即執(zhí)行優(yōu)先級(jí)高于宏任務(wù)。