Lawrence Eagles Senior full-stack developer, writer, and instructor.

Building custom Node.js event emitters

6 min read 1956

Node Logo Over Wire Connectors

Introduction: System events vs. custom events

Events in Node.js are actions that occur in our application that we can respond to. There are two different types of events in Node.js, both of which we’ll cover in this article.

1. System events: System events occur from the C++ side of the Node.js core and are handled by a C library used in Node.js called libuv.

The libuv library deals with lower-level events that are happening inside of the computer system, essentially, low-level events that come from the OS, such as receiving data from the internet, finished reading a file, and so on.

Because the flow of a Node.js program is determined by events (event-driven programming), all I/O requests would eventually generate a completion/failure event.

Side note: JavaScript does not have built-in capabilities to handle system events.

2. Custom events: Custom events occur inside the JavaScript core in Node.js. These events are handled by a Node.js class called the EventEmitter.

The EventEmitter is a JavaScript class in Node.js implemented to handle custom events because JavaScript does not have any built-in capability to handle events otherwise.

Often, when an event occurs in libuv, libuv generates custom JavaScript events so they can be easily handled. Consequently, Node.js events might be seen as a single entity even though they are actually two different types of events.

In this article, our focus will be on custom JavaScript events and the Event emitter.

What are event emitters?

Node.js uses the event-driven programming pattern as noted above. This design pattern is for producing and consuming events and is predominantly used in the frontend (browser). User actions such as a button click or a keypress generate an event.

We made a custom demo for .
No really. Click here to check it out.

These events have associated functions called listeners, which are called to handle their corresponding events when they are emitted.

The event-driven pattern has two main components:

  1. An event handler (listener). This is the function that would be called when the event is emitted. More than one listener can be listening for an event. When the event is emitted, these listeners would be called synchronously.
  2. A main loop that listens for events and calls the associated listeners to handle that event.

Node.js brings event-driven programming to the backend through the Event Emitter class. The Event Emitter class is a part of the events core Node.js module. It provides a way for us to emit and handle events, and it exposes — among many others — the on and the emit methods.

In this article, we will build a custom event emitter by using the core principles that powers Node.js Event Emitters so that we can better understand Node.js event emitters.

The code below shows how to emit and handle event using the Event Emitter object.

const EventEmitter = require('events');
const eventEmitter = new EventEmitter ();

eventEmitter.on('greet', () => {
  console.log('Hello world!');
});

eventEmitter.emit('greet');

In the code above, the eventEmitter.on method is used to register listeners that handle an event, while the eventEmitter.emit method is used to emit events.

By emitting the greet event above, the function that listens for the greet event is called and Hello world! is logged to the console.

Other methods exposed by the Event Emitter objects are:

  1. eventEmitter.once(): This adds a one-time listener
  2. eventEmitter.off(): This is an alias for eventEmitter.removeListener(). It removes an event listener from an event
  3. eventEmitter.listnersCount(): This returns the number of listeners listening to an event

In Node.js, there are other objects that can emit events. However, all objects that emit events are an instance of the Events Emitter class. To best understand the Event Emitter, let’s build our own.

The general pattern for a custom event emitter

The general idea of an Event Emitter is to have an event object whose keys act as custom events, and the corresponding values would be arrays of listeners that are invoked synchronously when the event is emitted.

Kindly consider the code below:

const GreetHandlers = [
  ()=> {console.log("Hello world!"), 
  ()=> {console.log("Hello Developer!"), 
  ()=> {console.log("Hello From LogRocket!"}
];

const events = {
  "greet": GreetHandlers
}

Here, the events object has one property: "greet". Every property name of this object is considered a unique event, meaning that the "greet" property is an event. The value of the "greet" property (event) is the GreetHandlers array. This is an array containing functions (or listeners) that would be called synchronously when the "greet" event occurs.

To call these functions synchronously, we loop through the array and invoke each function, as seen below:

GreetHandlers.forEach(listener => {
 listener();
});

// Output:
// "Hello world!"
// "Hello Developer!"
// "Hello From LogRocket!"

Our example above gives a simplified overview of the pattern that is used in the Node.js Event Emitter. We will employ the same pattern as we build our own Event Emitter in the next section.

Building a custom event emitter

Although the Node.js Event Emitter is a JavaScript class, we will be building our own using a function constructor so that we can understand what is happening in the background.

Classes in JavaScript give us a new and easy syntax to work with the JavaScript’s prototypal inheritance. Because classes in JavaScript are syntactic sugar for the prototypal pattern, many things occur under the hood that is hidden from us.

To build our custom Event Emitter, follow the steps below:

  1. Create the Event Emitter function constructor. This should have one property (the event object).
function Emitter( ) {
    this.events = { };
}

The events object above would serve as the main object that holds all custom events.

Each key and value of this object corresponds to an event and its array of listeners.

function Emitter( ) {
    this.events = {
      "greet": [
                  ()=> {},
                  ()=> {},
                  ()=> {}, 
              ],
      "speak": [
                  ()=> {},
                  ()=> {},
                  ()=> {}, 
              ]
}
  1. Add methods to the Event Emitter prototype.

Object-oriented (OOP) JavaScript gives us a clean way to share properties and methods across our app. This is because the prototypal pattern allows objects to access properties down the prototype chain, meaning an object can access properties in its prototype, in the prototype of its prototype, and beyond.

The JavaScript engine first searches for a method or property in the prototype of an object if it is absent in the object. If it does not find it in the prototype of that object, it continues its search down the prototype chain. This pattern of inheritance is known as prototypal inheritance.

Because of JavaScript’s prototypal inheritance, when we add properties and method to an object’s prototype, all instances of that object would have access to them.

See here in the code below, where we add the on method:

Emitter.prototype.on = function (type, listener) {
    // check if the listener is a function and throw error if it is not
    if(typeof listener !== "function") {
        throw new Error("Listener must be a function!")
    }
    // create the event listener property (array) if it does not exist.
    this.events[type] = this.events[type] || []; 
    // adds listners to the events array.
    this.events[type].push(listener); 
}

The code above adds the on function to the prototype of the Emitter object, which allows all instances of the Emitter object to inherit this method.

The on method takes two arguments namely: type and listener (a function).

First, the on method checks if the listener is a function. If it is not, it throws an error as seen below:

if(typeof listener !== "function") {
    throw new Error("Listener must be a function!")
}

Also, the on method checks if the type of event is present in the events object (as a key). If it isn’t present, it adds an event (as a key) to the events object and sets its value to an empty array. Finally, it adds listeners to the corresponding event array: this.events[type].push(listener);.

Now let’s add the emit method:

Emitter.prototype.emit = function(type) {
    if (this.events[type]) { // checks if event is a property on Emitter
        this.events[type].forEach(function(listener) { 
        // loop through that events array and invoke all the listeners inside it.
            listener( );
        })
    }
 }

// if used as a node module. Exports this function constructor
modules.exports = Emitter; 
// This makes it available from the require() 
// so we can make as many instances of it as we want.

The code above adds the emit method to the Emitters prototype. It simply checks if the event type is present (as a key) in the events object. If it is present, it then invokes all the corresponding listeners as already discussed above.

The code this.events[type] returns the value of the corresponding event property, which is an array containing listeners.

Consequently, the code below loops through the array and invokes all its listeners synchronously.

this.events[type].forEach(function(listener) { 
    // loop through that events array and invoke all the listeners inside it.
        listener( );
})

To use our Event Emitter, we would have to manually emit an event.

// if used in a Node.js environment require the module as seen below.
const Emitter = require('./emitter'); 

const eventEmitter = new Emitter();

eventEmitter.on('greet', ()=> {
  console.log("Hello World!");
});

eventEmitter.on('greet', ()=> {
  console.log("Hello from LogRocket!");
});

eventEmitter.emit("greet"); // manually emit an event 

In the code above, we first require and create an instance of the Emitter module using this:

const Emitter = require('./emitter'); 

const eventEmitter = new Emitter(); 

Then we assign listeners to the "greet" event using the on method.

eventEmitter.on('greet', ()=> {
  console.log("Hello World!");
});

eventEmitter.on('greet', ()=> {
  console.log("Hello from LogRocket!");
});

Finally, we manually emit the greet event using this line:

emtr.emit("greet");

Code Results

The image above shows the result of running our code. Get the full code in plain JavaScript.

Adding more event methods

The addListener method

It is important to note that the on method is actually an alias for the addListener method in the Node.js Event Emitter. Thus, we need to refactor out implementation.

Emitter.prototype.addListener = function (type, listener) {
  // check if the listener is a function and throw error if it is not
  if (typeof listener !== "function") {
    throw new Error("Listener must be a function!");
  }
  // create the event listener property (array) if it does not exist.
  this.events[type] = this.events[type] || [];
  // adds listners to the events array.
  this.events[type].push(listener);
};
Emitter.prototype.on = function (type, listener) {
  return this.addListener(type, listener);
};

The above code still works, but in this version, both the addListener and the on method do the same thing.

The listenersCount method

There’s also the listenersCount method. This returns the total number of functions (listeners) that are listening for a particular event. We will implement this below:

Emitter.prototype.listenerCount = function (type) {
  let listnersCount = 0;
  let listeners = this.events[type] || [];
  listnersCount = listners.length;
  console.log("listeners listnersCount", listnersCount);
  return listnersCount;
};

Here, we tell the simple store the array of listeners for the specified event to the listeners variable using:

let listeners = this.events[type] || []; 

If the event is not found, an empty array is stored. Then, we return the length of the listener variable.

The Event Emitter in Node.js follows this same idea but it has a lot of extra features. We just built a simple version.

You can play with final code in plain JavaScript here.

Conclusion

The event emitter is a fundamental building block of many parts of the Node.js JavaScript core.

All Node.js objects (such as streams and the HTTP module) that emit events are instances of the Event Emitter class.

It is an important object in Node.js that is defined and exposed by the events module.

Through the Event Emitter class, Node.js brings event-driven programming to the server-side.

I hope by building our small contrived Event Emitter, you have been able to learn more about the Node.js Event Emitter class.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Lawrence Eagles Senior full-stack developer, writer, and instructor.

One Reply to “Building custom Node.js event emitters”

Leave a Reply