In Node, many objects emit events. For example, a TCP server will transmit a "connect" event whenever a client requests a connection, and for example, whenever a whole piece of data is read, the file system will transmit a "data" event. These objects are called event emitters in Node. The event transmitter allows programmers to subscribe to events of interest and bind the callback function to the related events, so that the callback function is called whenever the event transmitter emits an event. The publish/subscribe mode is very similar to the traditional GUI mode, such as the program will receive corresponding notifications when the button is clicked. Using this model, server programs can react when some events occur, such as client connection, data available on the socket, or file closed.
You can also create your own event transmitter. In fact, Node provides a special EventEmitter pseudo-class that can be used as a base class to create your own event transmitter.
Understand callback mode
Asynchronous programming does not use function return values to indicate the end of a function call, but adopts a subsequent pass style.
"Continuation-passing style" (CPS: Continuation-passing style) is a programming style, and process control is explicitly passed to the next operation...
CPS-style functions will accept a function as an extra parameter. This function is used to explicitly point out the next process of program control. When the CPS function calculates its "return value", it will call the function that represents the next process of the program and take 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 files into memory under Node:
The code copy is as follows:
var fs = require('fs');
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 using CPS programming because you hand over the subsequent process of the 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 for CPS programming in Node.
Understand event transmitter mode
In the standard callback mode, a function is passed as a parameter to the function to be executed. This mode works very well in scenarios where the client needs to be notified after the function is completed. However, if multiple events occur during the execution of a function or events occur multiple times, this pattern is not suitable. For example, if you want to get notified every time the socket receives available data, you will find that the standard callback mode is not very useful in this scenario. At this time, the event transmitter mode comes in handy. You can use a set of standard interfaces to clearly separate the event generator and event listener.
When using event generator mode, two or more objects are involved—event transmitters and one or more event listeners.
An event transmitter, as the name suggests, is an object that can generate events. The event listener is code bound to the event transmitter to listen for specific types of events, like the following example:
The code copy is as follows:
var req = http.request(options, function(response) {
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 two necessary steps when creating an HTTP request to access a remote HTTP server using Node's http.request API (see later chapter). The first line adopts the "continuation-passing style" (CPS: Continuation-passing style), passing an inline function that will be called when HTTP responds. 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 the HTTP response object is passed to it as a parameter. This HTTP response object is an event transmitter. According to the Node document, it can emit many events including data and end. The callback functions you register will be called every time the event occurs.
As a lesson, use CPS mode when you need to regain execution rights after the requested operation is completed, and use event transmitter mode when events can occur multiple times.
Understand event types
The transmitted events have a type represented by a string. The previous example contains two event types: "data" and "end", which are any strings defined by the event transmitter. However, it is conventionally constitutive that event types are usually composed of lowercase words that do not contain empty characters.
You cannot use code to infer what types of events the event transmitter can generate, because the event transmitter API does not have an introspection mechanism, so the API you are using should have documentation to indicate that it can emit those types of events.
Once an event occurs, the event transmitter will call the listener related to the event and pass the relevant data to the listener as a parameter. In the previous example http.request, the "data" event callback function accepts a data object as its first and only parameter, while "end" does not accept any data. These parameters as part of the API contract are also subjectively defined by the author of the API. The parameter signatures of these callback functions will also be explained in the API documentation of each event emitter.
Although the event transmitter is an interface serving all types of events, the "error" event is a special implementation in Node. Most event transmitters 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 transmitter, the event transmitter will notice and throw an uncaught exception upward when the error occurs.
You can run the following code in Node PERL to test the effect, which simulates an event transmitter that can generate two events:
The code copy is as follows:
var em = new (require('events').EventEmitter)();
em.emit('event1');
em.emit('error', new Error('My mistake'));
You will see the following output:
The code copy is as follows:
var em = new (require('events').EventEmitter)();
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.<anonymous> (repl.js:182:12)
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.<anonymous> (readline.js:82:12)
>
In line 2 of the code, an event called "event1" is emitted, without any effect, but 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 an uncaught exception.
Using the Event Transmitter API
Any object that implements event transmitter mode (such as TCP Socket, HTTP request, etc.) implements the following set of methods:
The code copy is as follows:
.addListener and .on - Add event listener for events of the specified type
.once -- Bind an event listener that executes only once for the event of the specified type
.removeEventListener - Delete a listener bound to the specified event
.removeAllEventListener - Delete all listeners bound to the specified event
Let's introduce them in detail below.
Binding callback functions using .addListener() or .on()
By specifying the event type and callback function, you can register the actions performed when the event occurs. For example, if there are available data blocks when a file reads a data stream, it will emit a "data" event. The following code shows how to pass a callback function to let the program tell you that the data event has occurred.
The code copy is as follows:
function receiveData(data) {
console.log("got data from file read stream: %j", data);
}
readStream.addListener("data", receiveData);
You can also use .on, which is just the abbreviation of .addListener. The following code is the same as the above:
The code copy is as follows:
function receiveData(data) {
console.log("got data from file read stream: %j", data);
}
readStream.on("data", receiveData);
In the previous code, you can use a named function defined in advance as the callback function, and you can also use an inline anonymous function to simplify the code:
The code copy is as follows:
readStream.on("data", function(data) {
console.log("got data from file read stream: %j", data);
});
As mentioned earlier, the number of parameters and signatures passed to the callback function depend on the specific event transmitter object and event type. They are not standardized. The "data" event may pass a data buffer object, the "error" event passes an error object, and the "end" event of the data flow does not pass any data to the event listener.
Bind multiple event listeners
Event transmitter mode allows multiple event listeners to listen for the same event type of the same event transmitter, such as:
The code copy is as follows:
I have some data here.
I have some data here too.
The event transmitter is responsible for invoking all listeners bound on the specified event type in the order in which the listener is registered, that is:
1. When an event occurs, the event listener may not be called immediately, and there may be other event listeners called before it.
2. It is abnormal to be thrown to the stack, which may be because of a bug in the code. When an event is transmitted, if an event listener throws an exception when it is called, some event listeners may never be called. In this case, the event transmitter catches the exception and may also handle it.
See the following example:
The code copy is as follows:
readStream.on("data", function(data) {
throw new Error("Something wrong has happened");
});
readStream.on("data", function(data) {
console.log('I have some data here too.');
});
Because the first listener throws an exception, the second listener will not be called.
Use .removeListener() to remove an event listener from the event transmitter
If you no longer care about an event on an object, you can cancel the registered event listener by specifying the event type and callback function, like this:
The code copy is as follows:
function receiveData(data) {
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 that may be called at any time in the future from the event transmitter object.
In order to delete the listener, you must name the callback function, because the name of the callback function is required when adding and deleting it.
Use .once() to enable the callback function to be executed at most once
If you want to listen for an event that executes at most once, or are only interested in the first time an event occurs, you can use the .once() function:
The code copy is as follows:
function receiveData(data) {
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 a data event, the receiveData callback function will and will only be triggered once.
It is actually just a convenient method, because it can be implemented very simply, like this:
The code copy is as follows:
var EventEmitter = require("events").EventEmitter;
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 inherited from EventEmitter. The code simply uses the .on() method. Once an event is received, use .removeEventListener() to cancel the registration of the callback function and call the original callback function.
Note: The previous code uses the function.apply() method, which accepts an object and takes it as the contained this variable and an array of parameters. In the previous example, the unmodified parameter array is transparently passed to the callback function through the event transmitter.
Remove all event listeners from event transmitter with .removeAllListeners()
You can remove all listeners registered to the specified event type from the event transmitter as follows:
The code copy is as follows:
emitter.removeAllListeners(type);
For example, you can cancel the listener of all process interrupt signals like this:
The code copy is as follows:
process.removeAllListeners("SIGTERM");
Note: As a lesson, it is recommended that you only use this function when you know exactly what is deleted. Otherwise, you should let other parts of the application delete the event listener collection, or you can also let those parts of the program take charge of removing the listener themselves. But no matter what, this function is still useful in some rare scenarios, such as when you are preparing to close an event transmitter or shut down the entire process in an orderly manner.
Create an event transmitter
Event transmitters make programming interfaces more general in a great way. In a common and easy-to-understand programming mode, the client directly calls various functions, while in event transmitter mode, the client is bound to various events, which will make your program more flexible. (Translator's note: This sentence is not very confident, posting the original text: The event emitter provides a great way of making a programming interface more generic. When you use a common understanding pattern, clients bind to events instead of invoking functions, making your program more flexible.)
In addition, by using event transmitters, you can also obtain many features, such as binding multiple unrelated listeners on the same event.
Inherited from Node event transmitter
If you are interested in Node's event emitter mode and intend to use it in your own application, you can create a pseudo-class by inheriting EventEmitter:
The code copy is as follows:
util = require('util');
var EventEmitter = require('events').EventEmitter;
// This is the constructor of MyClass:
var MyClass = function() {
}
util.inherits(MyClass, EventEmitter);
Note: util.inherits creates 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 launch events like this:
The code copy is as follows:
MyClass.prototype.someMethod = function() {
this.emit("custom event", "argument 1", "argument 2");
};
In the above code, when the someMethond method is called by the MyClass instance, an event called "cuteom event" will be emitted. This event will also emit two strings as data: "argument 1" and "argument 2", which will be passed as parameters to the event listener.
The client of the MyClass instance can listen for the "custom event" event like this:
The code copy is as follows:
var myInstance = new MyClass();
myInstance.on('custom event', function(str1, str2) {
console.log('got a custom event with the str1 %s and str2 %s!', str1, str2);
});
For example, you can create a Ticker class that emits "tick" events once a second:
The code copy is as follows:
var util = require('util'),
EventEmitter = require('events').EventEmitter;
var Ticker = function() {
var self = this;
setInterval(function() {
self.emit('tick');
}, 1000);
};
util.inherits(Ticker, EventEmitter);
Clients using Ticker class can show how to use Ticker class and listen for "tick" events.
The code copy is as follows:
var ticker = new Ticker();
ticker.on("tick", function() {
console.log("tick");
});
summary
The event transmitter mode is a reentrance pattern that can be used to decouple the event transmitter object from a set of code for a specific event.
You can use event_emitter.on() to register listeners for specific types of events and unregister with event_emitter.removeListener().
You can also create your own event emitter by inheriting EventEmitter and simply using the .emit() function.