preface

Why write this article?

  • I clearly remember chatting with the interviewer about the event cycle when I started to work for Node, and the interviewer asked how the event was generated. Under what circumstances does an event occur…
  • In which scenarios are Events applied?
  • RxJava is an open source network request framework that encapsulates RxJava. It is also based on the publish-subscribe model, and the language is common, which is interesting. emoticons
  • The Events module is part of my node.js progression path

Koala is dedicated to sharing the complete Node.js technology stack, from JavaScript to Node.js, to back-end database. Wish you become an excellent senior Node.js engineer. [Programmer growth refers to north] Author, Github blog open source project github.com/koala-codin…

The interview will ask

Tell me where node.js applies the publish/subscribe model

Has the Events module been used in actual project development? What is the specific application scenario?

Is the execution order of Events listening functions asynchronous or synchronous?

What are some common functions of the Events module?

Simulate the implementation of Node.js core module Events

Github blog open source project github.com/koala-codin…

Publish/subscriber model

The publish/subscriber pattern is probably the most common design pattern I’ve encountered in development. The publish/subscriber pattern, also known as the messaging mechanism, defines a dependency that can be understood as 1 versus N (note: Observers monitor the corresponding state change of an object at the same time, and once the change is notified to all observers, thus triggering the corresponding events of the observers. This design mode solves the functional coupling between the subject object and the observer.

The Publish/subscriber model of life

Cops and thieves

In real life, the police caught the thief is a typical observer pattern “this to a recidivist in the street to go shopping and then caught for example”, where the thief was observed, the policemen is the observer, policemen always watched the thief, when the thief was stealing “gave policemen to send out a signal, in fact, the thief could not have told police I have steal”. The police got the signal and set out to catch the thief. This is the observer model

I subscribe to a newspaper

Life is like going to the newspaper office to order a newspaper. You can go to the newspaper office to pay for the subscription of what you like to read. When a new newspaper is published, the newspaper society will send a copy to everyone who subscribes to the newspaper, and the subscribers can receive it.

You subscribed to my official account

I this micro channel public number author is the publisher, you these micro channel users are subscribers “WHEN I send an article, concerned about [programmer growth refers to north] subscribers can receive the article.

Example code implementation and analysis

Take everyone subscription public number as an example to see how to implement the publish/subscribe model. We can add a type parameter to distinguish between subscribing to different types of public accounts. For example, some people subscribe to front-end public accounts, some people subscribe to Node.js public accounts, using this attribute to mark. This is more in line with EventEmitter’s source code, and another reason is that you can think of publish-subscriber patterns when you open a subscription number.)

The code is as follows:

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

This is the simplest implementation of the observer pattern. As you can see, the code is very simple. The core principle is to store the subscribed methods in an array by category, and then execute them when publishing

Next look at xiao Ming subscription [programmer growth refers to north] article code:

let xiaoming = {
    readArticle:function (info) {
        console.log('Received by Xiao Ming',info); }};let xiaogang = {
    readArticle:function (info) {
        console.log('Received by Xiaogang',info); }}; officeAccounts.subscribe('Programmer growth is north',xiaoming.readArticle);
officeAccounts.subscribe('Programmer growth is north',xiaogang.readArticle);
officeAccounts.subscribe('A Public Account',xiaoming.readArticle);

officeAccounts.unSubscribe('A Public Account',xiaoming.readArticle);

officeAccounts.publish('Programmer growth is north'.'Programmer growth refers to the Node article');
officeAccounts.publish('A Public Account'.'An article from a public account');

Copy the code

Running results:

Xiaogang received a Node article pointing to the north of programmer growthCopy the code
  • conclusion

By looking at three real life examples and code examples, the publish/subscribe model is indeed a 1-on-N relationship. All subscribers are notified when a publisher’s status changes.

  • Publish/subscribe model features and structure of three elements:
  1. The publisher
  2. The subscriber
  3. Events (Subscription)

Advantages and disadvantages of the publish/subscriber model

  • advantages

There is complete transparency between the principal and the observer, all messaging is done through the message dispatch center, that is, the specific business logic code will be in the message dispatch center, and there is complete loose coupling between the principal and the observer. Object direct decoupling, asynchronous programming, can be more loosely coupled code written.

  • disadvantages

Program legibility significantly decreased; When multiple publishers and subscribers are nested together, the program is hard to track, and the code is hard to read.

Relationship between EventEmitter and publish/subscribe

The EventEmitter module in Node.js uses the publish/subscribe design model. The publish/subscribe model introduces a message scheduling center between the subject and the observer, and the relationship between the subject and the observer is completely transparent. All the message transfer process is completed through the message scheduling center. This means that the specific business logic code will be done in the message dispatch center.

The basic components of an event

EventEmitter definition

Events is a highly used module in Node.js, and other native Node.js modules are based on it, such as streams, HTTP, etc. Its core idea is that the function of the Events module is an event binding and triggering, and all instances inherited from it have the ability of event processing.

Learn how to compare the official API source code of EventEs with the publish/subscribe model

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

The Events module

The Events module has only one EventEmitter class, which first defines the basic structure of the class

function EventEmitter() {// Private property, save the subscription method this._events = {}; } / / the default maximum number of listening to EventEmitter. DefaultMaxListeners = 10; module.exports = EventEmitter;Copy the code

On methods

The on method, which is used to subscribe to events (on and addListener are explained here), is assigned to the node.js source code in this way. If you know, you can tell me why.

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

EventEmitter.prototype.on = EventEmitter.prototype.addListener;
Copy the code

Here is our specific practice of on method:

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

        if (this._events[type]) {
            if (flag) {// Insert from the head
                this._events[type].unshift(listener);
            } else {
                this._events[type].push(listener); }}else {
            this._events[type] = [listener];
        }
		// Bind the event to trigger newListener
        if(type ! = ='newListener') {
            this.emit('newListener', type); }};Copy the code

Since other subclasses need to inherit from EventEmitter, we need to determine if the subclass has the _event attribute. We do this to ensure that subclasses must have this instance attribute. The flag is an insert flag for a subscription method, and if it is ‘true’ it is considered to be inserted at the head of the array. As you can see, this is the subscription method implementation of the Observer pattern.

Emit method

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

The emit method simply takes out the subscription method and executes it, using the call method to correct this pointing to an instance of a subclass.

Once method

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

    // An intermediate function that deletes the subscription immediately after the call
    function only() {
        listener();
        _this.removeListener(type, only);
    }
    //origin holds a reference to the original callback, used to determine when removing
    only.origin = listener;
    this.on(type, only);
};
Copy the code

The once method is interesting in that it subscribes to events “once” and then does not fire again. The idea is to wrap the subscribed method in a layer of functions that can be removed 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= > {
                    returnfn ! == listener && fn.origin ! == listener }); }};Copy the code

The off method unsubscribes, just like the observer mode, by removing the subscription method from the array.

PrependListener method

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

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

Other less commonly used apis

  • emitter.listenerCount(eventName)Can get event registrationlistenerThe number of
  • emitter.listeners(eventName)Can get event registrationlistenerArray copy.

A little exercise after learning the Api

/ / 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 parameters'.'arg2 parameters'); 
Copy the code

After executing the above code, the result is as follows:

$node event.js Listener1 arg1 parameter arg2 parameter Listener2 arg1 parameter arg2 parameterCopy the code

Instructions after handwritten code

Note the following when writing the Events module code:

  • Use the subscribe/publish model
  • What are the core components of the event
  • Consider some range and limit judgments when writing source code

Note: my handwritten code above is not the best performance and the most complete, the purpose is to take you to understand it first remember it. For example, the EventEmitter class was originally defined as this._events = {}.


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;
};
Copy the code

This._events = {}; this._events = {}; It is ok, but through Jsperf (a small egg, there is a need to search the following, check the performance tool) to compare the performance of the two, the source code is much higher, I will not specifically explain one by one, attach the source code address, interested can go to learn

Lib /events source address github.com/nodejs/node…

Source code is too long, to the address can be compared to continue to study, after all, is the public article, do not want to be said. But some doubt still want to tell, hey hey.

Read the source code after some questions to explain

Is the order in which the listener functions are executed synchronous or asynchronous?

Take a look at this 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((a)= > {
    console.log('I'm the output in asynchrony');
    resolve(1);
  }, 1000);
});
myEmitter.on('event'.function() {
  console.log('listener3');
});
myEmitter.emit('event');
console.log('end');
Copy the code

The following output is displayed:

Listener1 listener2 listener3 end I am the output in asyncCopy the code

When EventEmitter fires events, calls to listener functions are synchronous (note: these calls are synchronous, with the ‘end’ output coming last), but that doesn’t mean that listeners can’t include asynchronous code. The Listener2 event in this code adds an asynchronous function, which is the last output.

Under what circumstances are the events in the event loop generated? Under what circumstances?

The reason why I write this as a separate sub-title is because I find that many articles on the Internet are wrong, or unclear, causing misleading to people.

Look here, a paragraph from an API website, the specific website name will not be mentioned here, I do not want to invite hacking, this paragraph of content is no problem, but for those who are new to the event mechanism, it is easy to confuse

fs.open

An illustration of the process: in this figure, a detailed plot is drawn from the asynchronous call –> asynchronous call request encapsulation –> request object passed into the I/O thread pool to complete the I/O operation –> pass the completed I/O result to the I/O observer –> fetch callback function and result call execution from the I/O observer.

Events have

For events, look at the third part of the picture, the event loop. All node.js asynchronous I/O operations (net.server, fs.readstream, etc.) add an event to the event queue of the event loop upon completion.

Events trigger

Event triggering, we only need to focus on the third part of the diagram, where the event loop pulls the event processing out of the event queue. Fs. open generates events from objects that are instances of events.EventEmitter, which is a descendant of EventEmitter. When an event is removed from the event loop, it fires the event and the callback function.

The more you write, the more you think, the more you write, the more you think.

The event type is error

When we define an error event directly for EventEmitter, which contains the semantics of error, we often fire error events when we encounter exceptions.

When an error is emitted, EventEmitter states that if no listener is responding, Node.js will treat it as an exception and exit the program with an error message.

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

An error is 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) 
Copy the code

We usually set up listeners for objects that raise error events, so that the whole program will not crash after encountering an error.

How to change the maximum number of Listeners for EventEmitter?

By default, the maximum number of listeners for a single event is 10. If there are more than 10 listeners, the listener will still be executed, but the console will have a warning message, and the alarm message will contain the operation suggestion. Limits to maximum listeners can be adjusted by calling emitters. SetMaxListeners ()

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

Copy the code

A tip for printing warn details

The warning information above is not granular enough to tell us where the code went wrong. You can get more detailed information from process.on(‘warning’) (Emitter, event, eventCount).

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 }

Copy the code

Application scenarios for EventEmitter

  • It can be used for error exceptions that cannot be tried/caught
  • Many common modules inherit from EventEmitter for examplefsThe modulenetThe module
  • Interview questions will be taken
  • Publish/subscribe is often used in front-end development (the same idea as the Events module)

A few notes on publish/subscribe versus observer patterns

The observer model and the published-subscriber model, in normal times you can think of them as the same thing, but in some situations (like an interview) you might want to pay a little attention to how they compare and contrast

Let me borrow a picture from the Internet

  1. In the observer mode, there is coupling between the observer and the observed, and the two must know the existence of each other in order to carry out message transmission.
  2. In the publish-subscribe model, publishers and subscribers do not need to know about each other’s existence, and they communicate through a message broker, making the decoupling more complete.

Reference article:

  1. Node. Js’s official website
  2. Teacher Piao Ling’s Node.js is simple
  3. Events in github source address github.com/nodejs/node…
  4. JavaScript Design Patterns -SHERlocked93

Join us and learn!