[source code interpretation] thoroughly understand the Events module

Preface

Why write this article?

  • Remember clearly that I just got a job at node and talked to the interviewer about the event cycle. Then the interviewer asked how the event happened. Under what circumstances is an event...
  • In which scenarios have Events been applied?
  • Before that, it encapsulated an open source network request framework of RxJava, which is also based on publish subscribe mode. The languages are interlinked, which is very interesting. Emoticon
  • Events module is part of the advanced route of my public number Node.js

Interview will ask

Let's talk about where Node.js is applied to publish / subscribe mode.

Have Events modules been used in actual project development? What is the specific application scenario?

Are Events listener functions executed asynchronously or synchronously?

What are some common functions of Events module?

Simulate and implement the core module Events of Node.js

Github blog open source project https://github.com/koala-codi...

Publish / subscriber mode

The publish / subscriber pattern should be the most common design pattern I have encountered in the development process. The publish / subscribe mode, also known as message mechanism, defines a kind of dependency, which can be understood as 1-to-N (Note: not necessarily 1-to-many, sometimes 1-to-1). Observers monitor the corresponding state changes of an object at the same time, and notify all observers once the changes occur, thus triggering the corresponding events of observers. This design mode resolves It determines the coupling between the subject object and the observer.

Publishing / subscriber model in life

policeman and thief

In real life, the police catch thief is a typical observer mode: "this is a habitual offender who goes shopping in the street and is caught as an example." here the thief is the observed, and each policeman is the observer. The policeman always observes the thief. When the thief is stealing, "he sends a signal to the policeman, in fact, the thief can't tell the policeman that I have stolen something." do The police received a signal to attack the thief. This is an observer model.

Subscribed to a newspaper

In life, it's like going to a newspaper office to subscribe to a newspaper. When you publish a new newspaper, the newspaper society will send a copy to everyone who subscribes to the newspaper, and the subscriber can receive it.

You subscribed to my public account

The author of my wechat account is the publisher, and you wechat users are subscribers. "When I send an article, subscribers who pay attention to the growth of programmers can receive the article.

Code implementation and analysis of instances

Take public subscription as an example to see how the publish / subscribe mode is implemented. (for the reason of subscribing to newspapers as an example, you can add a type parameter to distinguish different types of public numbers subscribed. For example, some people subscribe to front-end public numbers, and some people subscribe to Node.js public numbers. Use this attribute to mark. This is more consistent with the EventEmitter source code to be discussed next. Another reason is that if you just open a subscription number article, you will think of the publish subscriber mode. )

The code is as follows:

let officeAccounts ={
    // Initialization defines a storage type object
    subscribes:{
        'any':[]
    },
    // Add subscription number
    subscribe:function(type='any',fn){
        if(!this.subscribes[type]){
            this.subscribes[type] = [];
        }
        this.subscribes[type].push(fn);//Store subscription method in array
    },
    // Unsubscribe
    unSubscribe:function(type='any',fn){
        this.subscribes[type] = 
        this.subscribes[type].filter((item)=>{
            return item!=fn;// Remove unsubscribed method from array 
        });
    },
    // Publish subscribe
    publish:function(type='any',...args){
        this.subscribes[type].forEach(item => {
            item(...args);// Call corresponding methods according to different types
        });
    }

}

The above is the implementation of the simplest observer mode. You can see that the code is very simple. The core principle is to store the subscription method in an array according to the classification, and take it out for execution when publishing.

Next, let's look at the code of Xiaoming's subscription to the article "programmer growth points north":

let xiaoming = {
    readArticle:function (info) {
        console.log('Xiaoming received it',info);
    }
};

let xiaogang = {
    readArticle:function (info) {
        console.log('Xiaogang received it',info);
    }
};

officeAccounts.subscribe('Programmer growth points north',xiaoming.readArticle);
officeAccounts.subscribe('Programmer growth points north',xiaogang.readArticle);
officeAccounts.subscribe('A public number',xiaoming.readArticle);

officeAccounts.unSubscribe('A public number',xiaoming.readArticle);

officeAccounts.publish('Programmer growth points north','Programmers grow to the North Node Article');
officeAccounts.publish('A public number','Articles of a public account');

Operation result:

Xiaoming receives Node articles about the growth of programmers
 Xiaogang received a Node article about the growth of programmers
  • conclusion

By observing three real-life examples and code examples, it is found that the publish / subscribe mode is indeed a 1-to-N relationship. When the publisher's state changes, all subscribers are notified.

  • Features and structure of publish / subscribe mode

Three elements:

  1. Publisher
  2. Subscriber
  3. Events (subscription)

Advantages and disadvantages of publish / subscriber mode

  • Advantage

The main body and the observer are completely transparent, and all the message delivery processes are completed through the message scheduling center, that is to say, the specific business logic code will be in the message scheduling center, and the main body and the observer are completely loosely coupled. Object direct decoupling, asynchronous programming, can be more loosely coupled code writing.

  • shortcoming

The readability of the program is significantly reduced; when multiple publishers and subscribers are nested together, the program is difficult to track, but in fact, the code is not easy to read.

The relationship between EventEmitter and publish / subscribe mode

EventEmitter in Node.js
The module uses the publish / subscribe design mode. The publish / subscribe mode introduces a message scheduling center between the subject and the observer, which is completely transparent between the subject and the observer. All the message delivery processes are completed through the message scheduling center, that is to say, the specific business logic code will be completed in the message scheduling center.

Basic elements of the event


Take a look at the Events module through Api comparison

Eventtwitter definition

Events is a highly used module in node.js. Other native node.js modules are based on it, such as flow, HTTP, etc. Its core idea is that the functions of events module are event binding and triggering, and all instances inherited from it have the ability of event processing.

Comparative study of some common official API source codes of events and publish / subscribe mode

The official Api explanation of this module is not to take you directly to learn the documents, but
Learn and remember the Api by comparing the publish / subscribe design pattern and writing the core code of a version of Events.

Events module

The Events module has only one EventEmitter class. First, define the basic structure of the class.

function EventEmitter() {
    //Private property, save subscription method
    this._events = {};
}

//Default maximum number of listeners

module.exports = EventEmitter;

on method

On method, which is used to subscribe to events (here under the instructions of on and addListener). In the source code of Node.js, they are assigned as follows. I don't know why. You can tell me why you do this.

EventEmitter.prototype.addListener = function addListener(type, listener) {
  return _addListener(this, type, listener, false);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

Next is our practice of on method:

EventEmitter.prototype.on =
    EventEmitter.prototype.addListener = function (type, listener, flag) {
        //Ensure instance properties exist
        if (!this._events) this._events = Object.create(null);

        if (this._events[type]) {
            if (flag) {//Insert from head
                this._events[type].unshift(listener);
            } else {
                this._events[type].push(listener);
            }

        } else {
            this._events[type] = [listener];
        }
        //Binding event, triggering newListener
        if (type !== 'newListener') {
            this.emit('newListener', type);
        }
    };

Because there are other subclasses that need to inherit from EventEmitter, it is necessary to determine whether the subclass has the "event" attribute, so as to ensure that the subclass must have this instance attribute. The flag tag is the insertion ID of a subscription method. If it is' true ', it is considered to be inserted in the header of the array. As you can see, this is the observer mode subscription method implementation.

emit method

EventEmitter.prototype.emit = function (type, ...args) {
    if (this._events[type]) {
        this._events[type].forEach(fn => fn.call(this, ...args));
    }
};

The emit method is to take the subscription method out for execution, and use the call method to correct the direction of this to point to the instance of the subclass.

once method

EventEmitter.prototype.once = function (type, listener) {
    let _this = this;

    //Intermediate function, delete the subscription immediately after the call
    function only() {
        listener();
        _this.removeListener(type, only);
    }
    //origin saves the reference of the original callback, which is used for judgment when removing
    only.origin = listener;
    this.on(type, only);
};

The once method is very interesting. Its function is to subscribe the event "once", when the event is triggered, it will not be triggered again. The principle is to wrap the subscription method with a layer of functions, and then remove the functions after execution.

off method

EventEmitter.prototype.off =
    EventEmitter.prototype.removeListener = function (type, listener) {

        if (this._events[type]) {
        //Filter out unsubscribed methods and remove them from the array
            this._events[type] =
                this._events[type].filter(fn => {
                    return fn !== listener && fn.origin !== listener
                });
        }
    };

The off method is called unsubscribe. The principle is the same as the observer mode. You can remove the subscription method from the array.

prependListener method

EventEmitter.prototype.prependListener = function (type, listener) {
    this.on(type, listener, true);
};

Code this method needless to say, call the on method to pass the tag to true (insert the subscription method in the header).
Above, the core method of EventEmitter class is implemented.

Other less commonly used APIs

  • Twitter.listenercount (eventName) can get the number of listeners registered for an event
  • Twitter.listeners (eventName) can get a copy of the listener array registered by the event.

Small exercises after Api learning

//event.js file
var events = require('events'); 
var emitter = new events.EventEmitter(); 
emitter.on('someEvent', function(arg1, arg2) { 
    console.log('listener1', arg1, arg2); 
}); 
emitter.on('someEvent', function(arg1, arg2) { 
    console.log('listener2', arg1, arg2); 
}); 
emitter.emit('someEvent', 'arg1 parameter', 'arg2 parameter'); 

Execute the above code, and the result is as follows:

$ node event.js 
listener1 arg1 parameter arg2 parameter
listener2 arg1 parameter arg2 parameter

Instructions after handwritten code

Note the following points when writing the Events module code:

  • Use subscription / publish mode
  • What are the core components of the event
  • Consider some scope and limit judgment when writing source code

Note: the above handwritten code is not the best and most perfect, just to let you know and remember him first. for instance:
In the initial definition of EventEmitter class, this is not defined directly in the source code. Please see:


function EventEmitter() {
  EventEmitter.init.call(this);
}

EventEmitter.init = function() {

  if (this._events === undefined ||
      this._events === Object.getPrototypeOf(this)._events) {
    this._events = Object.create(null);
    this._eventsCount = 0;
  }

  this._maxListeners = this._maxListeners || undefined;
};

It is also to implement a class, but pay more attention to the performance in the source code. We may think that a simple "this." "events = {}"; is OK, but by comparing the performance of jsperf (a small colored egg, search below if necessary, and view the performance tool), the source code is much higher, so I will not explain it one by one. Attach the source code address, and those who are interested can learn it.

lib/events source address https://github.com/nodejs/nod...

Source code length is too long, to address can be compared to continue to study, after all, is the public number article, do not want to be said. But some questions still need to be addressed.

Some questions after reading the source code

Is the execution order of listening function synchronous or asynchronous?

Look at a piece of code:

const EventEmitter = require('events');
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter();
myEmitter.on('event', function() {
  console.log('listener1');
});
myEmitter.on('event', async function() {
  console.log('listener2');
  setTimeout(() => {
    console.log('I am the output in asynchrony');
    resolve(1);
  }, 1000);
});
myEmitter.on('event', function() {
  console.log('listener3');
});
myEmitter.emit('event');
console.log('end');

The output results are as follows:

//Output results
listener1
listener2
listener3
end
 I am the output in asynchrony

When the EventEmitter triggers an event, the calls of each listening function are synchronous (Note: the calls of listening function are synchronous, and the output of 'end' is at the end), but it is not that the listening function cannot contain asynchronous code. In the code, the event of listener2 adds an asynchronous function, which is the last output.

Under what circumstances are events generated in an event loop? When did it trigger?

Why should I write this as a headline? I find that many articles on the Internet are wrong or not clear, which misleads us.

Look here. I won't tell you the specific website name of an API website. I don't want to recruit hackers. There's no problem with this content, but it's easy to confuse the little partners who just contacted the event mechanism.


Take fs.open as an example to see when the event is generated, when it is triggered, and what's the relationship with EventEmitter?

A description of the process: this figure shows in detail that starting from asynchronous call -- > asynchronous call request encapsulation -- > request object is passed into I/O thread pool to complete I/O operation -- > the completed I/O result is handed over to I/O observer -- > callback function and result call execution are taken out from I/O observer.

Event generation

You can see the third part of the figure about the event, where the event loop is. Node.js all asynchronous I/O operations (net.Server, fs.readStream, etc.) will add an event to the event queue of the event loop after completion.

Event triggering

For event triggering, we only need to pay attention to the third part of the figure. The event loop will take out the event processing in the event queue. The objects that fs.open generates events are instances of events.EventEmitter. They inherit EventEmitter. When events are retrieved from the event loop, this event and callback function are triggered.

The more you write, the more you think about it. It's always like this. You need to control it.

Problem with event type error

When we define an error event directly for EventEmitter, it contains the semantics of error, and we usually trigger the error event when we encounter an exception.

When the error is triggered, EventEmitter specifies that if there is no response listener, Node.js will treat it as an exception, exit the program and output the error information.

var events = require('events'); 
var emitter = new events.EventEmitter(); 
emitter.emit('error'); 

Error will be reported at runtime

node.js:201 
throw e; // process.nextTick error, or 'error' event on first tick 
^ 
Error: Uncaught, unspecified 'error' event. 
at EventEmitter.emit (events.js:50:15) 
at Object.<anonymous> (/home/byvoid/error.js:5:9) 
at Module._compile (module.js:441:26) 
at Object..js (module.js:459:10) 
at Module.load (module.js:348:31) 
at Function._load (module.js:308:12) 
at Array.0 (module.js:479:10) 
at EventEmitter._tickCallback (node.js:192:40) 

In general, we need to set the listener for the object that will trigger the error event to avoid the whole program crashing after encountering an error.

How to modify the maximum listening quantity of eventtwitter?

By default, the maximum number of listeners for a single event is 10. If there are more than 10, the listener will still execute, but there will be a warning message on the console. The warning message already prompts suggestions for operation. You can adjust the limit of the maximum listener by calling twitter. Setmaxlisteners().

(node:9379) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit

A little skill of printing the details of warn

The granularity of the warning information above is not enough to tell us where the code is wrong. You can get more specific information (twitter, event, eventCount) through process.on('warning ').

process.on('warning', (e) => {
  console.log(e);
})


{ MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 event listeners added. Use emitter.setMaxListeners() to increase limit
    at _addListener (events.js:289:19)
    at MyEmitter.prependListener (events.js:313:14)
    at Object.<anonymous> (/Users/xiji/workspace/learn/event-emitter/b.js:34:11)
    at Module._compile (module.js:641:30)
    at Object.Module._extensions..js (module.js:652:10)
    at Module.load (module.js:560:32)
    at tryModuleLoad (module.js:503:12)
    at Function.Module._load (module.js:495:3)
    at Function.Module.runMain (module.js:682:10)
    at startup (bootstrap_node.js:191:16)
  name: 'MaxListenersExceededWarning',
  emitter:
   MyEmitter {
     domain: null,
     _events: { event: [Array] },
     _eventsCount: 1,
     _maxListeners: undefined },
  type: 'event',
  count: 11 }

Application scenario of EventEmitter

  • An error exception that cannot try/catch can be thrown to use it
  • Many common modules inherit from EventEmitter

For example, fs module net module

  • Interview examination
  • Publish / subscribe mode is often used in front-end development (the idea is the same as Events module)

A description of publish / subscribe mode and observer mode

Observer mode and publish subscriber mode, you can think of them as one thing at ordinary times, but in some occasions (such as interview), you may need to pay attention to the difference between them.

Borrow a picture from the Internet


As you can see from the figure, there is an Event Channel in the publish subscribe mode.

  1. In the observer mode, there is still a coupling between the observer and the observed. Both of them must know the existence of the other party to carry out message transmission.
  2. In the publish subscribe mode, the publisher and the subscriber do not need to know each other's existence, they communicate through the message broker, and the decoupling is more thorough.

Reference article:

  1. Node.js official website
  2. Mr. Park Ling's Node.js
  3. Source address of events in github https://github.com/nodejs/nod...
  4. JavaScript Design Pattern elaboration-SHERlocked93

Join us to learn!

Keywords: node.js github Attribute network Programming

Added by mauri_gato on Thu, 17 Oct 2019 05:08:56 +0300