Node.js USES the event emitter pattern to implement event binding details

  • 2020-03-30 03:43:05
  • OfStack

In Node, many objects emit events. For example, a TCP server fires a "connect" event every time a client requests a connection, or a file system fires a "data" event every time a block of data is read. These objects are called event emitters in Node. Event emitters allow programmers to subscribe to events they are interested in and bind callbacks to related events so that the callback function is called whenever an event emitter fires an event. The publish/subscribe model is very similar to the traditional GUI model, where the program is notified when a button is clicked. With this mode, the server program can react to events such as a client connection, data available on the socket, or a file being closed.

You can also create your own EventEmitter. In fact, Node provides a pseudo-class called EventEmitter that you can use as a base class to create your own EventEmitter.

Understand the callback pattern

Asynchronous programming does not use the return value of a function to indicate the end of a function call, but instead USES a follow-on style.

CPS: continuation-passing style is a style of programming in which flow control is explicitly passed to the next step...

Cps-style functions take as an additional argument a function that explicitly indicates the next flow under program control. When the CPS function computes its "return value," it calls the function that represents the next flow of the program and takes the "return value" of the CPS function as its argument.

From wikipedia, http://en.wikipedia.org/wiki/Continuation-passing_style

In this style of programming, each function calls a callback after execution so that the program can continue. As you'll see later, JavaScript is perfect for this style of programming. Here's an example of loading a file into memory under Node:


var fs = require('fs'); fs.readFile('/etc/passwd', function(err, fileContent) {     if (err) {         throw err;     }     console.log('file content', fileContent.toString()); });

In this case, you're passing an inline anonymous function as the second argument to fs.readfile, which is essentially CPS programming because you're handing off the subsequent flow of execution to that callback function.

As you can see, the first argument to the callback function is an Error object, which is an instance of the Error class if an Error occurs, which is a common pattern in CPS programming in Node.

Understand the event emitter pattern

Standard callback mode, in which a function is passed as an argument to the function to be executed, works well in cases where the client needs to be notified when the function is complete. However, this pattern is not appropriate if multiple events occur during the execution of the function or if the event is repeated many times. For example, if you want to be notified every time the socket receives available data, you may find that the standard callback mode is not very useful, and then the event emitter mode comes in handy. You can use a standard interface to clearly separate the event generator from the event listener.

When using the event generator pattern, two or more objects are involved -- an event emitter and one or more event listeners.

An event emitter, as the name implies, is an object that can generate events. The event listener is the code bound to the event emitter to listen for certain types of events, as in the following example:


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 the two steps necessary to create an HTTP request to access a remote HTTP server using Node's http.request API (see the following section). The first line takes a "CPS: continuation-passing style," passing an inline function that is called when the HTTP response occurs. The reason the HTTP request API USES CPS here is that the program needs to wait until the http.request function has finished executing before continuing to do the following.

When http.request is executed, it will call the anonymous callback function and then pass the HTTP response object to it as a parameter. This HTTP response object is an event emitter. According to the Node document, it can launch many events, including data and end.

As a rule of thumb, use CPS mode when you need to reacquire execution after the requested operation is completed, and use event emitter mode when events can occur multiple times.

Understand the event type

The event that is emitted has a type represented by a string. The previous example contains two event types, "data" and "end", which are arbitrary strings defined by the event emitter, but by convention the event type is usually composed of lowercase words that do not contain null characters.

You can't 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 be documented to indicate which types of events it can emit.

Once an event occurs, the event emitter invokes the listener associated with the event and passes the relevant data as a parameter to the listener. . In front of the HTTP request that example, the "data" event callback function accepts a data object as the parameter, it is the first and only the "end" does not accept any data, these parameters as part of the API contract is defined by the API, author of the subjective, the parameters of the callback function signatures in the API documentation of each event emitter.

The event emitter is an interface that serves all types of events, but 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 for an "error" event in an event emitter, the event emitter will notice and throw an uncaught exception upward when the error occurs.

You can test this by running the following code in Node PERL, which simulates an event emitter that can produce two kinds of events:


var em = new (require('events').EventEmitter)(); em.emit('event1'); em.emit('error', new Error('My mistake'));

You will see the following output:


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, an event called "event1" is emitted at random, with no effect, but when an "error" event is emitted, the error is thrown onto the stack. If the program is not running in a PERL command line environment, the program will crash because of an uncaught exception.

Use the event emitter API

Any object that implements the event emitter pattern (such as a TCP Socket, HTTP request, etc.) implements the following set of methods:


.addListener and .on - Adds an event listener for an event of the specified type
.once - Binds an event listener that executes only once for an event of the specified type
.removeEventListener - Removes a listener bound to the specified event
.removeAllEventListener - Deletes all listeners bound to the specified event

Let's talk about them in detail.

Bind the callback function using. AddListener () or. On ()

By specifying the event type and callback function, you can register the action to be performed when the event occurs. For example, when a file reads a data stream, if there is a block of data available, it fires a "data" event. The following code shows how to tell the program that a data event has occurred by passing in a callback function.


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 a shorthand for.addlistener, and the following code is the same as the one above:


function receiveData(data) {    console.log("got data from file read stream: %j", data); }
readStream.on( " data " , receiveData);

The previous code, using a pre-defined named function as the callback function, you can also use an inline anonymous function to simplify the code:


readStream.on("data", function(data) {    console.log("got data from file read stream: %j", data); });

Said earlier, is passed to the callback function of the number of parameters and the signature depends on the event emitter object and event types, they are not been standardized, "data" event object can transfer a data buffer, a mistake pass "error" event object, the data flow "end" event is not to give any data to the event listener.

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. 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 listeners are registered, that is:

1. The event listener may not be called immediately after the event occurs, and other event listeners may be called before it.
2. Exceptions being thrown onto the stack is an abnormal behavior, possibly because of a bug in the code. When an event is fired, if an event listener throws an exception while being called, some event listeners may never be called. In this case, the event emitter catches the exception and may handle it.

Here's an example:


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 is not called.

Remove an event listener from the event emitter with. RemoveListener ()

If you no longer care about an event in an object, you can cancel the registered event listener by specifying the event type and callback function, like this:


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 from the event emitter object that may be called at any time in the future.

In order to remove listeners, you have to name the callback function, because you need the name of the callback function to add and remove.

Use. Once () to have the callback function execute at most once

If you want to listen for an event to be executed at most once, or if you are only interested in the first time an event occurs, you can use the. Once () function:


function receiveData(data) {     console.log("got data from file read stream: %j", data); } readStream.once("data", receiveData);

In the code above, the receiveData function will only be called once. If the readStream object fires a data event, the receiveData callback function will and will only be triggered once.

It's just a convenience method, because it's so easy to implement, like this:


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 code above, you to reset the EventEmitter. Prototype. Once the function, but also heavy defines each inherited from EventEmitter once all of the object function. The code simply USES the.on() method, once the event is received, unregisters the callback function with.removeeventlistener (), and calls the original callback function.

Note: the previous code used the function.apply() method, which accepts an object as the included this variable, and an array of arguments. In the previous example, the unmodified array of arguments was passed transparently to the callback function via the event emitter.

Remove all event listeners from the event emitter with. RemoveAllListeners ()

You can remove all listeners registered on the specified event type from the event emitter as follows:


emitter.removeAllListeners(type);

For example, you can cancel the listener of all process interrupt signals by:


process.removeAllListeners("SIGTERM");

Note: as a rule of thumb, it is recommended that you only use this function if you know exactly what has been deleted. Otherwise, you should let the rest of the application remove the collection of event listeners, or you can let those parts of the program take care of removing the listeners themselves. However, this function can be useful in rare situations, such as when you are ready to shut down an event emitter or an entire process in an orderly fashion.

Create an event emitter

Event emitters are a great way to make the programming interface more general. In a common and understandable programming mode, the client directly calls various functions, while in the event emitter mode, the client is bound to various events, which makes your program more flexible. I'm not so sure about this. I'm not so sure about this. The event emitter provides a great way of making a programming interface more generic. When you use a common understood The pattern to clients to bind to home events invoking functions provides, Making your program more flexible.)

In addition, you can get many features by using event emitters, such as binding multiple unrelated listeners to the same event.

Inherits from the Node event emitter

If you are interested in Node's EventEmitter pattern and want to use it in your own application, you can create a pseudo-class by inheriting from EventEmitter:


util = require('util'); var EventEmitter = require('events').EventEmitter; //This is the constructor of MyClass: var MyClass = function() { } util.inherits(MyClass, EventEmitter);

Note: util.inherits establishes the MyClass primitive chain so that your MyClass instance can use the EventEmitter primitive method.

Launch event

By inheriting from EventEmitter, MyClass can emit events like this:


MyClass.prototype.someMethod = function() {     this.emit("custom event", "argument 1", "argument 2"); };

The above code, when the someMethond method is called by an instance of MyClass, fires an event called "cuteom event", which also fires two strings as data: "argument 1" and "argument 2", which are passed as arguments to the event listener.

The client of the MyClass instance can listen for the "custom event" event like this:


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 fires a "tick" event once per second:


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 with the Ticker class can show how to use the Ticker class and listen for "tick" events,


var ticker = new Ticker(); ticker.on("tick", function() {     console.log("tick"); });

summary

The event emitter pattern is a recurrent pattern that can be used to decouple event emitter objects from code for a particular set of events.

You can use event_emp.on () to register listeners for specific types of events and event_emp.removelistener () to unregister.

You can also create your own EventEmitter by inheriting EventEmitter and simply using the. Emit () function.


Related articles: