node.js事件驱动_了解Node.js事件驱动架构
node.js事件驅(qū)動(dòng)
by Samer Buna
通過(guò)Samer Buna
了解Node.js事件驅(qū)動(dòng)架構(gòu) (Understanding Node.js Event-Driven Architecture)
Update: This article is now part of my book “Node.js Beyond The Basics”.
更新:這篇文章現(xiàn)在是我的書(shū)《超越基礎(chǔ)的Node.js》的一部分。
Read the updated version of this content and more about Node at jscomplete.com/node-beyond-basics.
在jscomplete.com/node-beyond-basics中閱讀此內(nèi)容的更新版本以及有關(guān)Node的更多信息。
Most of Node’s objects — like HTTP requests, responses, and streams — implement the EventEmitter module so they can provide a way to emit and listen to events.
Node的大多數(shù)對(duì)象(例如HTTP請(qǐng)求,響應(yīng)和流)都實(shí)現(xiàn)EventEmitter模塊,因此它們可以提供一種發(fā)出和偵聽(tīng)事件的方式。
The simplest form of the event-driven nature is the callback style of some of the popular Node.js functions — for example, fs.readFile. In this analogy, the event will be fired once (when Node is ready to call the callback) and the callback acts as the event handler.
事件驅(qū)動(dòng)性質(zhì)的最簡(jiǎn)單形式是一些流行的Node.js函數(shù)的回調(diào)樣式,例如fs.readFile 。 以此類(lèi)推,事件將被觸發(fā)一次(當(dāng)Node準(zhǔn)備好調(diào)用回調(diào)時(shí)),并且回調(diào)充當(dāng)事件處理程序。
Let’s explore this basic form first.
讓我們首先探討這種基本形式。
準(zhǔn)備好后給我打電話(huà),Node! (Call me when you’re ready, Node!)
The original way Node handled asynchronous events was with callback. This was a long time ago, before JavaScript had native promises support and the async/await feature.
Node處理異步事件的原始方式是使用回調(diào)。 這是很久以前的事情,當(dāng)時(shí)JavaScript還沒(méi)有原生的Promise支持和異步/等待功能。
Callbacks are basically just functions that you pass to other functions. This is possible in JavaScript because functions are first class objects.
回調(diào)基本上只是傳遞給其他函數(shù)的函數(shù)。 在JavaScript中這是可能的,因?yàn)楹瘮?shù)是第一類(lèi)對(duì)象。
It’s important to understand that callbacks do not indicate an asynchronous call in the code. A function can call the callback both synchronously and asynchronously.
重要的是要了解回調(diào)不會(huì)在代碼中指示異步調(diào)用。 函數(shù)可以同步和異步調(diào)用回調(diào)。
For example, here’s a host function fileSize that accepts a callback function cb and can invoke that callback function both synchronously and asynchronously based on a condition:
例如,下面是一個(gè)宿主函數(shù)fileSize ,它接受一個(gè)回調(diào)函數(shù)cb并可以根據(jù)條件同步和異步調(diào)用該回調(diào)函數(shù):
function fileSize (fileName, cb) {if (typeof fileName !== 'string') {return cb(new TypeError('argument should be string')); // Sync}fs.stat(fileName, (err, stats) => {if (err) { return cb(err); } // Asynccb(null, stats.size); // Async}); }Note that this is a bad practice that leads to unexpected errors. Design host functions to consume callback either always synchronously or always asynchronously.
請(qǐng)注意,這是一種不良做法,會(huì)導(dǎo)致意外錯(cuò)誤。 設(shè)計(jì)主機(jī)函數(shù)以始終同步或始終異步使用回調(diào)。
Let’s explore a simple example of a typical asynchronous Node function that’s written with a callback style:
讓我們研究一下用回調(diào)樣式編寫(xiě)的典型異步Node函數(shù)的簡(jiǎn)單示例:
const readFileAsArray = function(file, cb) {fs.readFile(file, function(err, data) {if (err) {return cb(err);}const lines = data.toString().trim().split('\n');cb(null, lines);}); };readFileAsArray takes a file path and a callback function. It reads the file content, splits it into an array of lines, and calls the callback function with that array.
readFileAsArray采用文件路徑和回調(diào)函數(shù)。 它讀取文件內(nèi)容,將其拆分為一個(gè)行數(shù)組,然后使用該數(shù)組調(diào)用回調(diào)函數(shù)。
Here’s an example use for it. Assuming that we have the file numbers.txt in the same directory with content like this:
這是一個(gè)示例用法。 假設(shè)我們?cè)谕荒夸浿杏形募umbers.txt ,其內(nèi)容如下:
10 11 12 13 14 15If we have a task to count the odd numbers in that file, we can use readFileAsArray to simplify the code:
如果我們有一個(gè)任務(wù)來(lái)計(jì)算該文件中的奇數(shù),則可以使用readFileAsArray簡(jiǎn)化代碼:
readFileAsArray('./numbers.txt', (err, lines) => {if (err) throw err;const numbers = lines.map(Number);const oddNumbers = numbers.filter(n => n%2 === 1);console.log('Odd numbers count:', oddNumbers.length); });The code reads the numbers content into an array of strings, parses them as numbers, and counts the odd ones.
該代碼將數(shù)字內(nèi)容讀入字符串?dāng)?shù)組,將其解析為數(shù)字,然后對(duì)奇數(shù)進(jìn)行計(jì)數(shù)。
Node’s callback style is used purely here. The callback has an error-first argument err that’s nullable and we pass the callback as the last argument for the host function. You should always do that in your functions because users will probably assume that. Make the host function receive the callback as its last argument and make the callback expect an error object as its first argument.
純粹在這里使用Node的回調(diào)樣式。 回調(diào)函數(shù)的錯(cuò)誤優(yōu)先參數(shù)err可為空,我們將回調(diào)函數(shù)作為主機(jī)函數(shù)的最后一個(gè)參數(shù)傳遞。 您應(yīng)該始終在函數(shù)中執(zhí)行此操作,因?yàn)橛脩?hù)可能會(huì)假設(shè)這樣做。 使主機(jī)函數(shù)將回調(diào)作為其最后一個(gè)參數(shù)接收,并使回調(diào)將錯(cuò)誤對(duì)象作為其第一個(gè)參數(shù)。
替代回調(diào)的現(xiàn)代JavaScript (The modern JavaScript alternative to Callbacks)
In modern JavaScript, we have promise objects. Promises can be an alternative to callbacks for asynchronous APIs. Instead of passing a callback as an argument and handling the error in the same place, a promise object allows us to handle success and error cases separately and it also allows us to chain multiple asynchronous calls instead of nesting them.
在現(xiàn)代JavaScript中,我們有promise對(duì)象。 承諾可以替代異步API的回調(diào)。 Promise對(duì)象無(wú)需將回調(diào)作為參數(shù)傳遞并在同一位置處理錯(cuò)誤,而是使我們可以分別處理成功和錯(cuò)誤情況,還可以鏈接多個(gè)異步調(diào)用而不是嵌套它們。
If the readFileAsArray function supports promises, we can use it as follows:
如果readFileAsArray函數(shù)支持promise,則可以按以下方式使用它:
readFileAsArray('./numbers.txt').then(lines => {const numbers = lines.map(Number);const oddNumbers = numbers.filter(n => n%2 === 1);console.log('Odd numbers count:', oddNumbers.length);}).catch(console.error);Instead of passing in a callback function, we called a .then function on the return value of the host function. This .then function usually gives us access to the same lines array that we get in the callback version, and we can do our processing on it as before. To handle errors, we add a .catch call on the result and that gives us access to an error when it happens.
我們沒(méi)有傳遞回調(diào)函數(shù),而是在宿主函數(shù)的返回值上調(diào)用了.then函數(shù)。 這個(gè).then函數(shù)通常使我們能夠訪(fǎng)問(wèn)與回調(diào)版本中相同的lines數(shù)組,并且我們可以像以前一樣對(duì)其進(jìn)行處理。 為了處理錯(cuò)誤,我們?cè)诮Y(jié)果上添加了.catch調(diào)用,使我們可以在錯(cuò)誤發(fā)生時(shí)對(duì)其進(jìn)行訪(fǎng)問(wèn)。
Making the host function support a promise interface is easier in modern JavaScript thanks to the new Promise object. Here’s the readFileAsArray function modified to support a promise interface in addition to the callback interface it already supports:
由于有了新的Promise對(duì)象,在現(xiàn)代JavaScript中使宿主函數(shù)支持Promise接口更加容易。 這是已修改的readFileAsArray函數(shù),除了已經(jīng)支持的回調(diào)接口之外,還支持Promise接口:
const readFileAsArray = function(file, cb = () => {}) {return new Promise((resolve, reject) => {fs.readFile(file, function(err, data) {if (err) {reject(err);return cb(err);}const lines = data.toString().trim().split('\n');resolve(lines);cb(null, lines);});}); };So we make the function return a Promise object, which wraps the fs.readFile async call. The promise object exposes two arguments, a resolve function and a reject function.
因此,我們使函數(shù)返回一個(gè)Promise對(duì)象,該對(duì)象包裝了fs.readFile異步調(diào)用。 Promise對(duì)象公開(kāi)兩個(gè)參數(shù),一個(gè)resolve函數(shù)和一個(gè)reject函數(shù)。
Whenever we want to invoke the callback with an error we use the promise reject function as well, and whenever we want to invoke the callback with data we use the promise resolve function as well.
每當(dāng)我們想用錯(cuò)誤調(diào)用回調(diào)函數(shù)時(shí),我們也會(huì)使用promise reject函數(shù),每當(dāng)我們想對(duì)數(shù)據(jù)調(diào)用回調(diào)函數(shù)時(shí),我們也將使用promise resolve函數(shù)。
The only other thing we needed to do in this case is to have a default value for this callback argument in case the code is being used with the promise interface. We can use a simple, default empty function in the argument for that case: () => {}.
在這種情況下,我們唯一需要做的另一件事就是為該回調(diào)參數(shù)設(shè)置一個(gè)默認(rèn)值,以防代碼與promise接口一起使用。 在這種情況下,我們可以在參數(shù)中使用一個(gè)簡(jiǎn)單的默認(rèn)空函數(shù): () => {}。
使用async / await消費(fèi)諾言 (Consuming promises with async/await)
Adding a promise interface makes your code a lot easier to work with when there is a need to loop over an async function. With callbacks, things become messy.
當(dāng)需要循環(huán)異步功能時(shí),添加一個(gè)promise接口會(huì)使您的代碼更容易使用。 使用回調(diào),事情變得混亂。
Promises improve that a little bit, and function generators improve on that a little bit more. This said, a more recent alternative to working with async code is to use the async function, which allows us to treat async code as if it was synchronous, making it a lot more readable overall.
承諾會(huì)有所改善,函數(shù)生成器會(huì)有所改善。 這就是說(shuō),使用異步代碼的另一種替代方法是使用async函數(shù),該函數(shù)使我們可以將異步代碼視為同步代碼,從而使整體可讀性更高。
Here’s how we can consume the readFileAsArray function with async/await:
這是我們?nèi)绾卧赼sync / await中使用readFileAsArray函數(shù):
async function countOdd () {try {const lines = await readFileAsArray('./numbers');const numbers = lines.map(Number);const oddCount = numbers.filter(n => n%2 === 1).length;console.log('Odd numbers count:', oddCount);} catch(err) {console.error(err);} } countOdd();We first create an async function, which is just a normal function with the word async before it. Inside the async function, we call the readFileAsArray function as if it returns the lines variable, and to make that work, we use the keyword await. After that, we continue the code as if the readFileAsArray call was synchronous.
我們首先創(chuàng)建一個(gè)異步函數(shù),這只是一個(gè)普通函數(shù),其前面帶有單詞async 。 在async函數(shù)內(nèi)部,我們調(diào)用readFileAsArray函數(shù),就好像它返回lines變量一樣,為了使其正常工作,我們使用關(guān)鍵字await 。 之后,我們繼續(xù)執(zhí)行代碼,就像readFileAsArray調(diào)用是同步的一樣。
To get things to run, we execute the async function. This is very simple and more readable. To work with errors, we need to wrap the async call in a try/catch statement.
為了使事情運(yùn)行,我們執(zhí)行異步功能。 這非常簡(jiǎn)單并且可讀性強(qiáng)。 要處理錯(cuò)誤,我們需要將異步調(diào)用包裝在try / catch語(yǔ)句中。
With this async/await feature, we did not have to use any special API (like .then and .catch). We just labeled functions differently and used pure JavaScript for the code.
使用此異步/等待功能,我們不必使用任何特殊的API(例如.then和.catch)。 我們只是對(duì)函數(shù)進(jìn)行了不同的標(biāo)記,并對(duì)代碼使用了純JavaScript。
We can use the async/await feature with any function that supports a promise interface. However, we can’t use it with callback-style async functions (like setTimeout for example).
我們可以將async / await功能與任何支持promise接口的功能一起使用。 但是,我們不能將其與回調(diào)樣式的異步函數(shù)(例如setTimeout)一起使用。
EventEmitter模塊 (The EventEmitter Module)
The EventEmitter is a module that facilitates communication between objects in Node. EventEmitter is at the core of Node asynchronous event-driven architecture. Many of Node’s built-in modules inherit from EventEmitter.
EventEmitter是一個(gè)模塊,可促進(jìn)Node中對(duì)象之間的通信。 EventEmitter是Node異步事件驅(qū)動(dòng)的體系結(jié)構(gòu)的核心。 Node的許多內(nèi)置模塊都繼承自EventEmitter。
The concept is simple: emitter objects emit named events that cause previously registered listeners to be called. So, an emitter object basically has two main features:
這個(gè)概念很簡(jiǎn)單:發(fā)射器對(duì)象發(fā)出命名事件,這些事件導(dǎo)致先前注冊(cè)的偵聽(tīng)器被調(diào)用。 因此,發(fā)射器對(duì)象基本上具有兩個(gè)主要功能:
- Emitting name events. 發(fā)出名稱(chēng)事件。
- Registering and unregistering listener functions. 注冊(cè)和注銷(xiāo)偵聽(tīng)器功能。
To work with the EventEmitter, we just create a class that extends EventEmitter.
要使用EventEmitter,我們只需創(chuàng)建一個(gè)擴(kuò)展EventEmitter的類(lèi)。
class MyEmitter extends EventEmitter {}Emitter objects are what we instantiate from the EventEmitter-based classes:
發(fā)射器對(duì)象是我們從基于EventEmitter的類(lèi)中實(shí)例化的:
const myEmitter = new MyEmitter();At any point in the lifecycle of those emitter objects, we can use the emit function to emit any named event we want.
在這些發(fā)射器對(duì)象的生命周期中的任何時(shí)候,我們都可以使用發(fā)出函數(shù)來(lái)發(fā)出我們想要的任何命名事件。
myEmitter.emit('something-happened');Emitting an event is the signal that some condition has occurred. This condition is usually about a state change in the emitting object.
發(fā)出事件是已發(fā)生某種情況的信號(hào)。 該條件通常與發(fā)射物體的狀態(tài)變化有關(guān)。
We can add listener functions using the on method, and those listener functions will be executed every time the emitter object emits their associated name event.
我們可以使用on方法添加偵聽(tīng)器函數(shù),這些偵聽(tīng)器函數(shù)將在每次發(fā)射器對(duì)象發(fā)出其關(guān)聯(lián)的名稱(chēng)事件時(shí)執(zhí)行。
事件!==異步 (Events !== Asynchrony)
Let’s take a look at an example:
讓我們看一個(gè)例子:
const EventEmitter = require('events');class WithLog extends EventEmitter {execute(taskFunc) {console.log('Before executing');this.emit('begin');taskFunc();this.emit('end');console.log('After executing');} }const withLog = new WithLog();withLog.on('begin', () => console.log('About to execute')); withLog.on('end', () => console.log('Done with execute'));withLog.execute(() => console.log('*** Executing task ***'));Class WithLog is an event emitter. It defines one instance function execute. This execute function receives one argument, a task function, and wraps its execution with log statements. It fires events before and after the execution.
WithLog類(lèi)是事件發(fā)射器。 它定義了一個(gè)實(shí)例函數(shù)execute 。 該execute函數(shù)接收一個(gè)參數(shù),一個(gè)task函數(shù),并用log語(yǔ)句包裝其執(zhí)行。 它在執(zhí)行前后觸發(fā)事件。
To see the sequence of what will happen here, we register listeners on both named events and finally execute a sample task to trigger things.
要查看此處發(fā)生的順序,我們?cè)趦蓚€(gè)命名事件上注冊(cè)偵聽(tīng)器,最后執(zhí)行一個(gè)示例任務(wù)來(lái)觸發(fā)事件。
Here’s the output of that:
這是輸出:
Before executing About to execute *** Executing task *** Done with execute After executingWhat I want you to notice about the output above is that it all happens synchronously. There is nothing asynchronous about this code.
我希望您注意到上面的輸出,所有操作都是同步發(fā)生的。 此代碼沒(méi)有異步的。
- We get the “Before executing” line first. 我們首先得到“執(zhí)行之前”這一行。
The begin named event then causes the “About to execute” line.
然后, begin命名事件將導(dǎo)致“關(guān)于要執(zhí)行”行。
- The actual execution line then outputs the “*** Executing task ***” line. 然后,實(shí)際執(zhí)行行將輸出“ ***執(zhí)行任務(wù)***”行。
The end named event then causes the “Done with execute” line
然后,以命名end事件導(dǎo)致“完成并執(zhí)行”行
- We get the “After executing” line last. 我們最后得到“執(zhí)行后”行。
Just like plain-old callbacks, do not assume that events mean synchronous or asynchronous code.
就像普通的回調(diào)一樣,不要假定事件表示同步或異步代碼。
This is important, because if we pass an asynchronous taskFunc to execute, the events emitted will no longer be accurate.
這很重要,因?yàn)槿绻覀儌鬟f異步taskFunc來(lái)execute ,則發(fā)出的事件將不再準(zhǔn)確。
We can simulate the case with a setImmediate call:
我們可以使用setImmediate調(diào)用來(lái)模擬這種情況:
// ...withLog.execute(() => {setImmediate(() => {console.log('*** Executing task ***')}); });Now the output would be:
現(xiàn)在的輸出將是:
Before executing About to execute Done with execute After executing *** Executing task ***This is wrong. The lines after the async call, which were caused the “Done with execute” and “After executing” calls, are not accurate any more.
錯(cuò)了 異步調(diào)用之后的行(導(dǎo)致“執(zhí)行完成”和“執(zhí)行后”調(diào)用)不再準(zhǔn)確。
To emit an event after an asynchronous function is done, we’ll need to combine callbacks (or promises) with this event-based communication. The example below demonstrates that.
為了在異步函數(shù)完成后發(fā)出事件,我們需要將回調(diào)(或promise)與基于事件的通信結(jié)合起來(lái)。 下面的示例演示了這一點(diǎn)。
One benefit of using events instead of regular callbacks is that we can react to the same signal multiple times by defining multiple listeners. To accomplish the same with callbacks, we have to write more logic inside the single available callback. Events are a great way for applications to allow multiple external plugins to build functionality on top of the application’s core. You can think of them as hook points to allow for customizing the story around a state change.
使用事件而不是常規(guī)回調(diào)的好處之一是,我們可以通過(guò)定義多個(gè)偵聽(tīng)器來(lái)對(duì)同一信號(hào)進(jìn)行多次響應(yīng)。 為實(shí)現(xiàn)回調(diào),我們必須在單個(gè)可用回調(diào)內(nèi)編寫(xiě)更多邏輯。 事件是應(yīng)用程序允許多個(gè)外部插件在應(yīng)用程序核心之上構(gòu)建功能的好方法。 您可以將它們視為掛鉤點(diǎn),以允許圍繞狀態(tài)更改自定義故事。
異步事件 (Asynchronous Events)
Let’s convert the synchronous sample example into something asynchronous and a little bit more useful.
讓我們將同步示例示例轉(zhuǎn)換為異步示例,然后再使用一些示例。
const fs = require('fs'); const EventEmitter = require('events');class WithTime extends EventEmitter {execute(asyncFunc, ...args) {this.emit('begin');console.time('execute');asyncFunc(...args, (err, data) => {if (err) {return this.emit('error', err);}this.emit('data', data);console.timeEnd('execute');this.emit('end');});} }const withTime = new WithTime();withTime.on('begin', () => console.log('About to execute')); withTime.on('end', () => console.log('Done with execute'));withTime.execute(fs.readFile, __filename);The WithTime class executes an asyncFunc and reports the time that’s taken by that asyncFunc using console.time and console.timeEnd calls. It emits the right sequence of events before and after the execution. And also emits error/data events to work with the usual signals of asynchronous calls.
該WithTime類(lèi)執(zhí)行的asyncFunc和報(bào)告,是采取由時(shí)間asyncFunc使用console.time和console.timeEnd電話(huà)。 它在執(zhí)行前后發(fā)出正確的事件序列。 并且還會(huì)發(fā)出錯(cuò)誤/數(shù)據(jù)事件以與異步調(diào)用的通常信號(hào)一起工作。
We test a withTime emitter by passing it an fs.readFile call, which is an asynchronous function. Instead of handling file data with a callback, we can now listen to the data event.
我們通過(guò)傳遞一個(gè)fs.readFile調(diào)用來(lái)測(cè)試withTime發(fā)射器,這是一個(gè)異步函數(shù)。 現(xiàn)在,我們可以偵聽(tīng)數(shù)據(jù)事件,而不是使用回調(diào)處理文件數(shù)據(jù)。
When we execute this code , we get the right sequence of events, as expected, and we get a reported time for the execution, which is helpful:
當(dāng)我們執(zhí)行此代碼時(shí),我們將按預(yù)期獲得正確的事件序列,并獲得執(zhí)行的報(bào)告時(shí)間,這很有幫助:
About to execute execute: 4.507ms Done with executeNote how we needed to combine a callback with an event emitter to accomplish that. If the asynFunc supported promises as well, we could use the async/await feature to do the same:
請(qǐng)注意,我們需要如何結(jié)合使用回調(diào)和事件發(fā)射器來(lái)實(shí)現(xiàn)這一點(diǎn)。 如果asynFunc支持promise,我們可以使用async / await功能執(zhí)行相同的操作:
class WithTime extends EventEmitter {async execute(asyncFunc, ...args) {this.emit('begin');try {console.time('execute');const data = await asyncFunc(...args);this.emit('data', data);console.timeEnd('execute');this.emit('end');} catch(err) {this.emit('error', err);}} }I don’t know about you, but this is much more readable to me than the callback-based code or any .then/.catch lines. The async/await feature brings us as close as possible to the JavaScript language itself, which I think is a big win.
我不了解您,但是比起基于回調(diào)的代碼或任何.then / .catch行,這對(duì)我而言更具可讀性。 異步/等待功能使我們盡可能接近JavaScript語(yǔ)言本身,我認(rèn)為這是一個(gè)巨大的勝利。
事件參數(shù)和錯(cuò)誤 (Events Arguments and Errors)
In the previous example, there were two events that were emitted with extra arguments.
在前面的示例中,有兩個(gè)帶有額外參數(shù)的事件。
The error event is emitted with an error object.
錯(cuò)誤事件與錯(cuò)誤對(duì)象一起發(fā)出。
this.emit('error', err);The data event is emitted with a data object.
數(shù)據(jù)事件與數(shù)據(jù)對(duì)象一起發(fā)出。
this.emit('data', data);We can use as many arguments as we need after the named event, and all these arguments will be available inside the listener functions we register for these named events.
在命名事件之后,我們可以根據(jù)需要使用任意數(shù)量的參數(shù),并且所有這些參數(shù)都將在我們?yōu)檫@些命名事件注冊(cè)的偵聽(tīng)器函數(shù)中可用。
For example, to work with the data event, the listener function that we register will get access to the data argument that was passed to the emitted event and that data object is exactly what the asyncFunc exposes.
例如,要處理數(shù)據(jù)事件,我們注冊(cè)的偵聽(tīng)器函數(shù)將可以訪(fǎng)問(wèn)傳遞給發(fā)出的事件的數(shù)據(jù)參數(shù),而該數(shù)據(jù)對(duì)象正是asyncFunc公開(kāi)的。
withTime.on('data', (data) => {// do something with data });The error event is usually a special one. In our callback-based example, if we don’t handle the error event with a listener, the node process will actually exit.
error事件通常是一個(gè)特殊的事件。 在基于回調(diào)的示例中,如果不使用偵聽(tīng)器處理錯(cuò)誤事件,則節(jié)點(diǎn)進(jìn)程實(shí)際上將退出。
To demonstrate that, make another call to the execute method with a bad argument:
為了證明這一點(diǎn),請(qǐng)使用錯(cuò)誤的參數(shù)再次調(diào)用execute方法:
class WithTime extends EventEmitter {execute(asyncFunc, ...args) {console.time('execute');asyncFunc(...args, (err, data) => {if (err) {return this.emit('error', err); // Not Handled}console.timeEnd('execute');});} }const withTime = new WithTime();withTime.execute(fs.readFile, ''); // BAD CALL withTime.execute(fs.readFile, __filename);The first execute call above will trigger an error. The node process is going to crash and exit:
上面的第一個(gè)execute調(diào)用將觸發(fā)錯(cuò)誤。 節(jié)點(diǎn)進(jìn)程將崩潰并退出:
events.js:163throw er; // Unhandled 'error' event^ Error: ENOENT: no such file or directory, open ''The second execute call will be affected by this crash and will potentially not get executed at all.
第二次執(zhí)行調(diào)用將受到此崩潰的影響,并且可能根本無(wú)法執(zhí)行。
If we register a listener for the special error event, the behavior of the node process will change. For example:
如果我們?yōu)樘厥鈋rror事件注冊(cè)一個(gè)偵聽(tīng)器,則節(jié)點(diǎn)進(jìn)程的行為將改變。 例如:
withTime.on('error', (err) => {// do something with err, for example log it somewhereconsole.log(err) });If we do the above, the error from the first execute call will be reported but the node process will not crash and exit. The other execute call will finish normally:
如果執(zhí)行上述操作,將報(bào)告來(lái)自第一個(gè)執(zhí)行調(diào)用的錯(cuò)誤,但節(jié)點(diǎn)進(jìn)程不會(huì)崩潰并退出。 另一個(gè)執(zhí)行調(diào)用將正常完成:
{ Error: ENOENT: no such file or directory, open '' errno: -2, code: 'ENOENT', syscall: 'open', path: '' } execute: 4.276msNote that Node currently behaves differently with promise-based functions and just outputs a warning, but that will eventually change:
請(qǐng)注意,Node當(dāng)前與基于promise的功能的行為有所不同,只是輸出警告,但最終會(huì)改變:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: ENOENT: no such file or directory, open '' DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.The other way to handle exceptions from emitted errors is to register a listener for the global uncaughtException process event. However, catching errors globally with that event is a bad idea.
處理來(lái)自發(fā)出的錯(cuò)誤的異常的另一種方法是為全局uncaughtException流程事件注冊(cè)一個(gè)偵聽(tīng)器。 但是,在該事件中全局捕獲錯(cuò)誤不是一個(gè)好主意。
The standard advice about uncaughtException is to avoid using it, but if you must do (say to report what happened or do cleanups), you should just let the process exit anyway:
關(guān)于uncaughtException的標(biāo)準(zhǔn)建議是避免使用它,但是,如果必須這樣做(例如報(bào)告所發(fā)生的事情或進(jìn)行清理),則無(wú)論如何都要讓該過(guò)程退出:
process.on('uncaughtException', (err) => {// something went unhandled.// Do any cleanup and exit anyway!console.error(err); // don't do just that.// FORCE exit the process too.process.exit(1); });However, imagine that multiple error events happen at the exact same time. This means the uncaughtException listener above will be triggered multiple times, which might be a problem for some cleanup code. An example of this is when multiple calls are made to a database shutdown action.
但是,想象一下,多個(gè)錯(cuò)誤事件恰好同時(shí)發(fā)生。 這意味著上面的uncaughtException偵聽(tīng)器將被多次觸發(fā),這對(duì)于某些清理代碼可能是一個(gè)問(wèn)題。 例如,當(dāng)多次調(diào)用數(shù)據(jù)庫(kù)關(guān)閉操作時(shí)。
The EventEmitter module exposes a once method. This method signals to invoke the listener just once, not every time it happens. So, this is a practical use case to use with the uncaughtException because with the first uncaught exception we’ll start doing the cleanup and we know that we’re going to exit the process anyway.
EventEmitter模塊公開(kāi)once方法。 此方法發(fā)出信號(hào)僅一次調(diào)用偵聽(tīng)器,而不是每次都調(diào)用。 因此,這是一個(gè)與uncaughtException一起使用的實(shí)際用例,因?yàn)閷?duì)于第一個(gè)未捕獲的異常,我們將開(kāi)始進(jìn)行清理,并且我們知道無(wú)論如何都將退出該過(guò)程。
聽(tīng)眾順序 (Order of Listeners)
If we register multiple listeners for the same event, the invocation of those listeners will be in order. The first listener that we register is the first listener that gets invoked.
如果我們?yōu)橥皇录?cè)多個(gè)偵聽(tīng)器,則這些偵聽(tīng)器的調(diào)用將是有序的。 我們注冊(cè)的第一個(gè)偵聽(tīng)器是被調(diào)用的第一個(gè)偵聽(tīng)器。
// ????? withTime.on('data', (data) => {console.log(`Length: ${data.length}`); });// ????? withTime.on('data', (data) => {console.log(`Characters: ${data.toString().length}`); });withTime.execute(fs.readFile, __filename);The above code will cause the “Length” line to be logged before the “Characters” line, because that’s the order in which we defined those listeners.
上面的代碼將導(dǎo)致“長(zhǎng)度”行記錄在“字符”行之前,因?yàn)檫@是我們定義這些偵聽(tīng)器的順序。
If you need to define a new listener, but have that listener invoked first, you can use the prependListener method:
如果您需要定義一個(gè)新的偵聽(tīng)器,但首先要調(diào)用該偵聽(tīng)器,則可以使用prependListener方法:
// ????? withTime.on('data', (data) => {console.log(`Length: ${data.length}`); });// ????? withTime.prependListener('data', (data) => {console.log(`Characters: ${data.toString().length}`); });withTime.execute(fs.readFile, __filename);The above will cause the “Characters” line to be logged first.
以上將導(dǎo)致“字符”行被首先記錄。
And finally, if you need to remove a listener, you can use the removeListener method.
最后,如果需要?jiǎng)h除偵聽(tīng)器,則可以使用removeListener方法。
That’s all I have for this topic. Thanks for reading! Until next time!
這就是我要做的所有事情。 謝謝閱讀! 直到下一次!
Learning React or Node? Checkout my books:
學(xué)習(xí)React還是Node? 結(jié)帳我的書(shū):
Learn React.js by Building Games
通過(guò)構(gòu)建游戲?qū)W習(xí)React.js
Node.js Beyond the Basics
超越基礎(chǔ)的Node.js
翻譯自: https://www.freecodecamp.org/news/understanding-node-js-event-driven-architecture-223292fcbc2d/
node.js事件驅(qū)動(dòng)
總結(jié)
以上是生活随笔為你收集整理的node.js事件驱动_了解Node.js事件驱动架构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 网易云音乐的算法有什么特点_当算法设计音
- 下一篇: 梦到自己在雨中站着是啥意思