Home > Article > Web Front-end > Detailed explanation of event binding using event emitter pattern in Node.js_node.js
In Node, many objects emit events. For example, a TCP server will emit a "connect" event every time a client requests a connection. For example, every time a whole block of data is read, the file system will emit a "data" event. These objects are called event emitters in Node. Event emitters allow programmers to subscribe to the events they are interested in and bind callback functions to the relevant events, so that the callback function is called every time the event emitter emits an event. The publish/subscribe mode is very similar to the traditional GUI mode. For example, when a button is clicked, the program will receive the corresponding notification. Using this mode, the server program can react when some events occur, such as when a client connects, data is available on the socket, or when the file is closed.
You can also create your own event emitter. In fact, Node provides an EventEmitter pseudo-class, which can be used as a base class to create your own event emitter.
Understanding the callback pattern
Asynchronous programming does not use function return values to indicate the end of function calls, but adopts the successor delivery style.
"Continuation-passing style" (CPS: Continuation-passing style) is a programming style in which flow control is explicitly passed to the next operation...
The CPS style function will accept a function as an extra parameter. This function is used to explicitly indicate the next process controlled by the program. When the CPS function calculates its "return value", it will call the function that represents the next step of the program. function of a process and takes the "return value" of the CPS function as its parameter.
From Wikipedia - http://en.wikipedia.org/wiki/Continuation-passing_style
In this programming style, each function will call a callback function after execution, so that the program can continue to run. You will understand later that JavaScript is very suitable for this programming style. Here is an example of loading a file into memory under Node:
fs.readFile('/etc/passwd', function(err, fileContent) {
if (err) {
throw err;
}
console.log('file content', fileContent.toString());
});
In this example, you pass an inline anonymous function as the second parameter of fs.readFile. In fact, this is programming using CPS, because you hand over the subsequent process of program execution to the callback function.
As you can see, the first parameter of the callback function is an error object. If an error occurs in the program, this parameter will be an instance of the Error class. This is a common pattern in CPS programming in Node.
Understanding the Event Emitter Pattern
In the standard callback mode, a function is passed as a parameter to the function to be executed. This mode works well in scenarios where the client needs to be notified after the function is completed. However, this model is not suitable if multiple events occur during the execution of the function or if the event occurs repeatedly multiple times. For example, you want to be notified every time the socket receives available data. In this scenario, you will find that the standard callback mode is not very easy to use. At this time, the event emitter mode comes in handy. You can use a set of standard interfaces to Clear separation of event generators and event listeners.
When using the event generator pattern, two or more objects are involved - an event emitter and one or more event listeners.
Event emitter, as the name suggests, is an object that can generate events. The event listener is the code bound to the event emitter to listen for specific types of events, like the following example:
response.on("data", function(data) {
console.log("some data from the response", data);
});
response.on("end", function() {
console.log("response ended");
});
});
req.end();
This code demonstrates the two steps necessary to create an HTTP request to access a remote HTTP server using Node's http.request API (see later chapters). The first line uses the "continuation-passing style" (CPS: Continuation-passing style), passing an inline function that will be called when the HTTP response is made. The HTTP request API uses CPS here because the program needs to continue to perform subsequent operations after the http.request function is executed.
When http.request is executed, the anonymous callback function will be called, and then the HTTP response object will be passed to it as a parameter. This HTTP response object is an event emitter. According to the Node documentation, it can emit data, end at For many events, the callback functions you registered will be called every time the event occurs.
As a rule of thumb, use CPS pattern when you need to regain execution rights after the requested operation is completed, and use Event Emitter pattern when an event can occur multiple times.
Understanding event types
Emitted events have a type represented by a string. The previous example includes two event types: "data" and "end". They are arbitrary strings defined by the event emitter, but the convention is , event types usually consist of lowercase words that do not contain null characters.
You cannot use code to infer what types of events an event emitter can produce, because the event emitter API does not have an introspection mechanism, so the API you use should have documentation to show what types of events it can emit.
Once an event occurs, the event emitter will call the listener related to the event and pass the relevant data to the listener as a parameter. In the previous http.request example, the "data" event callback function accepts a data object as its first and only parameter, while "end" does not accept any data. These parameters are also determined by the API author as part of the API contract. Subjectively defined, the parameter signatures of these callback functions are also described in the API documentation of each event emitter.
Although the event emitter is an interface that serves all types of events, the "error" event is a special implementation in Node. Most event emitters in Node will generate an "error" event when an error occurs in the program. If the program does not listen to the "error" event of an event emitter, the event emitter will notice and throw it up when an error occurs. An uncaught exception.
You can run the following code in Node PERL to test the effect. It simulates an event emitter that can generate two events:
em.emit('event1');
em.emit('error', new Error('My mistake'));
You will see the following output:
undefined
> em.emit('event1');
false
> em.emit('error', new Error('My mistake'));
Error: My mistake
at repl:1:18
at REPLServer.eval (repl.js:80:21)
at repl.js:190:20
at REPLServer.eval (repl.js:87:5)
at Interface.
at Interface.emit (events.js:67:17)
at Interface._onLine (readline.js:162:10)
at Interface._line (readline.js:426:8)
at Interface._ttyWrite (readline.js:603:14)
at ReadStream.
>
In line 2 of the code, an event called "event1" is randomly emitted, which has no effect. However, when the "error" event is emitted, the error is thrown to the stack. If the program is not running in the PERL command line environment, the program will crash due to uncaught exceptions.
Using the Event Emitter API
Any object that implements the event emitter pattern (such as TCP Socket, HTTP request, etc.) implements the following set of methods:
We introduce them in detail below.
Use .addListener() or .on() to bind the callback function
By specifying the event type and callback function, you can register actions to be performed when the event occurs. For example, if there is an available data block when the file reads the data stream, a "data" event will be emitted. The following code shows how to let the program tell you that a data event has occurred by passing in a callback function.
console.log("got data from file read stream: %j", data);
}
readStream.addListener(“data”, receiveData);
You can also use .on, which is just shorthand for .addListener. The following code is the same as the above:
console.log("got data from file read stream: %j", data);
}
readStream.on(“data”, receiveData);
The previous code uses a predefined named function as the callback function. You can also use an inline anonymous function to simplify the code:
console.log("got data from file read stream: %j", data);
});
Bind multiple event listeners
The event emitter pattern allows multiple event listeners to listen to the same event type of the same event emitter, such as:
I have some data here too.
The event emitter is responsible for calling all listeners bound on the specified event type in the order in which the listeners were registered, that is:
1. When an event occurs, the event listener may not be called immediately. There may be other event listeners called before it.
2. It is abnormal behavior for exceptions to be thrown onto the stack. It may be because there is a bug in the code. When an event is emitted, if an event listener throws an exception when it is called, it may cause some event listeners to is never called. In this case, the event emitter catches the exception and perhaps handles it.
Look at this example:
throw new Error("Something wrong has happened");
});
readStream.on("data", function(data) {
console.log('I have some data here too.');
});
Because the first listener threw an exception, the second listener will not be called.
Remove an event listener from an event emitter using .removeListener()
If you no longer care about an event of an object, you can cancel the registered event listener by specifying the event type and callback function, like this:
console.log("got data from file read stream: %j", data);
}
readStream.on("data", receiveData);
// ...
readStream.removeListener("data", receiveData);
In this example, the last line removes an event listener from the event emitter object that may be called at any time in the future.
In order to delete the listener, you must name the callback function, because the name of the callback function is needed when adding and removing.
Use .once() to execute the callback function at most once
If you want to monitor an event that can be executed at most once, or are only interested in the first time an event occurs, you can use the .once() function:
console.log("got data from file read stream: %j", data);
}
readStream.once("data", receiveData);
In the above code, the receiveData function will only be called once. If the readStream object emits the data event, the receiveData callback function will be triggered only once.
It is actually just a convenience method, because it is very simple to implement, like this:
EventEmitter.prototype.once = function(type, callback) {
var that = this;
this.on(type, function listener() {
that.removeListener(type, listener);
callback.apply(that, arguments);
});
};
In the above code, you redefine the EventEmitter.prototype.once function, and also redefine the once function of each object that inherits from EventEmitter. The code simply uses the .on() method. Once the event is received, it uses .removeEventListener() to cancel the registration of the callback function and call the original callback function.
Note: The function.apply() method is used in the previous code, which accepts an object and uses it as the contained this variable, as well as a parameter array. In the previous example, the unmodified parameter array is passed transparently to the callback function through the event emitter.
Remove all event listeners from the event emitter using .removeAllListeners()
You can remove all listeners registered on a specified event type from an event emitter as follows:
For example, you can cancel all process interrupt signal listeners like this:
Note: As a rule of thumb, it is recommended that you only use this function if you know exactly what to delete. Otherwise, you should let other parts of the application delete the event listener collection, or you can let those parts of the application take responsibility for themselves. Remove the listener. But in any case, this function is still very useful in some rare scenarios, such as when you are preparing to shut down an event emitter in an orderly manner or shut down the entire process.
Create event emitter
Event emitters are a great way to make programming interfaces more versatile. In a common and easy-to-understand programming pattern, the client directly calls various functions, while in the event emitter pattern, the client is bound to various events, this will make your program more flexible. (Translator's note: This sentence is not very confident, so I posted the original text: The event emitter provides a great way of making a programming interface more generic. When you use a common understood pattern, clients bind to events instead of invoking functions, making your program more flexible.)
In addition, by using event emitters, you can also get many features, such as binding multiple unrelated listeners to the same event.
Inherits from Node event emitter
If you are interested in Node's event emitter pattern and plan to use it in your own application, you can create a pseudo-class by inheriting EventEmitter:
var EventEmitter = require('events').EventEmitter;
// This is the constructor of MyClass:
var MyClass = function() {
}
util.inherits(MyClass, EventEmitter);
Note: util.inherits establishes the prototype chain of MyClass so that your MyClass instance can use the prototype method of EventEmitter.
Launch event
By inheriting from EventEmitter, MyClass can emit events like this:
this.emit("custom event", "argument 1", "argument 2");
};
In the above code, when the someMethod method is called by an instance of MyClass, an event called "cuteom event" will be emitted. This event will also emit two strings as data: "argument 1" and "argument 2" , they will be passed as parameters to the event listener.
Clients of MyClass instances can listen for "custom event" events like this:
myInstance.on('custom event', function(str1, str2) {
console.log('got a custom event with the str1 %s and str2 %s!', str1, str2);
});
For another example, you can create a Ticker class that emits a "tick" event every second:
EventEmitter = require('events').EventEmitter;
var Ticker = function() {
var self = this;
setInterval(function() {
self.emit('tick');
}, 1000);
};
util.inherits(Ticker, EventEmitter);
The client using the Ticker class can show how to use the Ticker class and listen for the "tick" event,
ticker.on("tick", function() {
console.log("tick");
});
Summary
The event emitter pattern is a reentrant pattern that can be used to decouple an event emitter object from a set of event-specific code.
You can use event_emitter.on() to register a listener for a specific type of event, and event_emitter.removeListener() to unregister.
You can also create your own event emitter by inheriting EventEmitter and simply using the .emit() function.