NodeJs Event Emitters — for beginners and for experts

November 16, 2019

The core of NodeJs is event-driven programming. In NodeJs, we achieve event-driven programming by Event Emitter class.




In this article, we will discuss everything about the EventEmitter class. The following are the topics that I will be covering in this article.
  • What is an Event Emitter?
  • Creating an Event Emitter
  • Attaching event listeners and publishing events to an event emitter
  • Maintaining a single event emitter instance application-wide
  • Is it synchronous or asynchronous?
  • Order of Execution of Event Emitters
  • How & Where NodeJs internally use EventEmitters?
  • Some useful member functions of EventEmitter Class

Event Emitters

EventEmitter is a class that helps us creating a publisher-subscriber pattern in NodeJs.
With an Event Emitter, we can simply raise a new event from a different part of an application and a listener will listen to the raised event and have some action performed for the event.

Creating an Event Emitter

To create an event emitter, we need to create an instance of the event emitter instance from the ‘events’ module in NodeJs.
I am using Typescript instead of JavaScript.
import { EventEmitter } from 'events';const eventEmitter = new EventEmitter();
This will create an EventEmitter instance. But what to do with it? Let's see it in action.

Publishing Events and Listening to them

EventEmitter class comes with a lot of member functions. We will be using these member functions to publish events and listen to them.
Below are the member functions in the EventEmitter class.

For now, we will only pay attention to these two member functions:
  • on(eventName, …)
  • emit(eventName, …)
To publish an event, we use the emit() function and to listen to an event, we use the on() function.
In EventEmitters, we publish and listen to the events by name.
In the last code snippet, where we created an EventEmitter object, we use the following code to raise events and listen to them.
import { EventEmitter } from 'events';const eventEmitter = new EventEmitter();// listen to the event
eventEmitter.on('myEvent', () => {
    console.log('Data Received');
});// publish an event
eventEmitter.emit('myEvent');
Running the above code snippet prints the following output in the console:
> Data Received
In the above code snippet, we raised an event with the name myEvent on the last line and we had a listener registered to the event just above the line of publishing event.
At the time of publishing the event, there must be an EventEmitter listener existing to listen to the published event.
For example, if we change the above code snippet to
import { EventEmitter } from ‘events’;
const eventEmitter = new EventEmitter();eventEmitter.on(‘myEvent’, () => {
    console.log(‘Listener 1’);
});eventEmitter.emit(‘myEvent’);eventEmitter.on(“myEvent”, () => {
    console.log(“Listener 2”);
});
Output:
> Listener 1
We get only Listener 1 as output in the console as the Listener 2 was registered after emitting the event.

EventEmitter Instance should be Singleton for a single Event Name

In other words, the on() and the emit() function must be called on the same EventEmitter instance.
The listeners will not work if registered on a separate EventEmitter instance.
import { EventEmitter } from 'events';const eventEmitter1 = new EventEmitter();
eventEmitter1.on('myEvent', () => {
    console.log('Listener');
});const eventEmitter2 = new EventEmitter();
eventEmitter2.emit('myEvent');
This will not print anything in the console as there were two separate instances used in this code. One instance for registering the publisher and the other instance for listening to the event.

Maintaining a single event emitter instance application-wide

A node application is generally 100’s of files. This gets challenging to maintain a single copy of the EventEmitter instance throughout the application.
There is a simple strategy to create and maintain a singleton copy for an EventEmitter instance.
When creating the EventEmitter instance, we can simply store it as an application-level setting using the app.set(<key>, <value>).
import { EventEmitter } from "events";
import express from 'express';const eventEmitter = new EventEmitter();
const app = express();app.set('eventEmitter', eventEmitter);// access it from any module of the application
console.log(app.get('eventEmitter'));

Is Event Emitter synchronous or asynchronous?

Consider the following code snippet
import { EventEmitter } from 'events';
const eventEmitter = new EventEmitter();eventEmitter.on('myEvent', (data) => {
    console.log(data);
});console.log('Statement A');
eventEmitter.emit('myEvent', 'Statement B');
console.log("Statement C");
When we execute this code snippet, we get the following output in the console
> Statement A
> Statement B
> Statement C
The events raised by Event Emitters are synchronously executed by the listeners in the current Event Loop’s iteration.
If you want to read more about Event Loops and how synchronous and asynchronous code runs in NodeJs, read the article HERE. 


Order of execution of the listeners

The listeners are executed in the order the listeners are created for an Event Emitter.
Consider the following code snippet to understand this statement:
import { EventEmitter } from 'events';
const eventEmitter = new EventEmitter();eventEmitter.on('myEvent', (data) => {
    console.log(data, '- FIRST');
});console.log('Statement A');eventEmitter.on("myEvent", data => {
    console.log(data, '- SECOND');
});eventEmitter.emit('myEvent', 'Emitted Statement');
console.log("Statement B");
When executed the above code gives the output:
> Statement A
> Emitted Statement - FIRST
> Emitted Statement - SECOND
> Statement B
The listener that is registered earlier, is executed earlier.

How & Where NodeJs internally uses EventEmitters

NodeJs internally uses EventEmitters widely across its environment. The first example that we can think of is “Streams”.
Streams extends EventEmitters
Streams are built on top of Event Emitters that raise pre-defined events like ‘open’, ‘end’, ‘data’, etc.
The following code snippet can be a simple example of a stream explaining the resemblance with EventEmitters.
import { createReadStream } from "fs";let chunkIndex = 0;
const readStream = createReadStream("./data.txt");readStream.on("open", () => {
    console.log("Started Reading...");
});readStream.on("end", () => {
    console.log("Completed Reading...");
});readStream.on("data", chunk => {
    console.log("Chunk: " + ++chunkIndex);
    console.log("-----------------------------------------");
    console.log(chunk);
    console.log("\n");
});
When executed, the code will give the following output:
Started Reading...Chunk: 1
----------------------------------------------------------
<Buffer 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f 6c 6f 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f 6e 73 65 63 74 65 74 75 72 20 61 64 69 70 69 73 63 69 6e 67 ... >Chunk: 2
----------------------------------------------------------
<Buffer 74 20 6e 75 6e 63 20 76 69 74 61 65 20 66 65 72 6d 65 6e 74 75 6d 2e 20 49 6e 20 75 74 20 61 72 63 75 20 74 65 6d 70 6f 72 2c 20 66 61 75 63 69 62 75 ... >Chunk: 3
----------------------------------------------------------
<Buffer 20 76 69 74 61 65 2c 20 65 67 65 73 74 61 73 20 69 64 20 73 65 6d 2e 20 44 6f 6e 65 63 20 75 74 20 75 6c 74 72 69 63 69 65 73 20 6c 6f 72 65 6d 2c 20 ... >Completed Reading...
Where data.txt is a large text file containing some random ‘Lorem Ipsum’ text
Notice here the listeners are called internally by the NodeJs. We do not need to call the listeners explicitly as NodeJs internally extends EventEmitters, exposes custom pre-defined events (data, open, end) and raises these events automatically when required for the Streams.
In the data listener, the Buffer gets printed instead of the string as NodeJs instead of reading the text content of the file, actually reads the file as Buffer.
Another example of NodeJs using Event Emitters is the global process variable.
process object exposes some variables that we can listen to and respond accordingly. Thes events are:
process.on(‘exit’)
process.on(‘uncaughtException’)
Following is the example showing their usage:
process.on("exit", () => console.log("Exit"));
process.on("beforeExit", () => console.log("Before Exit"));process.on('uncaughtException', () => {
    console.log('Exception');
    process.exit();
});throw new Error('Test Error');
Running the above code will print the following output in the console
Exception
Exit

Some useful member functions of EventEmitter Class

In the EventEmitter class, we have only looked at the .on() and the .emit() function. But there are other member functions available in the EventEmitter class.
Following is the list of all the member functions available in the EventEmitter class.


We will cover some of the useful member functions among them.


once()

With once(), the listener will be discarded after listening for an event. Events listened with once() will be triggered only once.
import { EventEmitter } from "events";
const eventEmitter = new EventEmitter();eventEmitter.on("myEvent", data => {
    console.log(data, "- ON");
});eventEmitter.once("myEvent", data => {
    console.log(data, "- ONCE");
});eventEmitter.emit("myEvent", "Emitted Statement");
eventEmitter.emit("myEvent", "Emitted Statement");
eventEmitter.emit("myEvent", "Emitted Statement");
Running the above code will give the output:
Emitted Statement - ON
Emitted Statement - ONCE
Emitted Statement - ON
Emitted Statement - ON
Notice here the once listener was only called once. After getting called for the first time, it will be discarded for further use.

eventNames()

Get all the active event names.
import { EventEmitter } from "events";const eventEmitter = new EventEmitter();eventEmitter.on("myEvent", data => console.log(data, "- ON"));
eventEmitter.on("myEvent2", data => console.log(data, "- ON"));
eventEmitter.once("myEvent3", data => console.log(data, "- ONCE"));console.log(eventEmitter.eventNames());eventEmitter.emit("myEvent3", 'EVENT');console.log(eventEmitter.eventNames());
Running the following code will give the output:
[ 'myEvent', 'myEvent2', 'myEvent3' ]
EVENT - ONCE
[ 'myEvent', 'myEvent2' ]
Notice after the once event gets triggered, calling the eventNames() does not gives its event name.

addListener()

It is exactly the same as on(). It is just an alias for event.on()

removeListener()

This is used to remove a listener.
import { EventEmitter } from "events";
const eventEmitter = new EventEmitter();function func1(): void {
    console.log("EVENT TRIGGERED");
}eventEmitter.on("myEvent", func1);
eventEmitter.on("myEvent2", func1);console.log(eventEmitter.eventNames());
eventEmitter.removeListener("myEvent", func1);
console.log(eventEmitter.eventNames());
Running the above code will gove the output as:
[ 'myEvent', 'myEvent2' ]
[ 'myEvent2' ]
To remove a listener, we need to pass the same function reference in the second parameter that was used to create a listener.

removeAllListeners()

This is used to remove all active event listeners from an EventEmitter instance.
import { EventEmitter } from "events";
const eventEmitter = new EventEmitter();function func1(): void {
    console.log("EVENT TRIGGERED");
}eventEmitter.on("myEvent", func1);
eventEmitter.on("myEvent2",func1);eventEmitter.removeAllListeners();
console.log(eventEmitter.eventNames());
Running the following code will give the output as an empty array
[]



That’s all about the Event Emitters in NodeJs. I tried covering all the related topics in this article.
Let me know in the comments section if I missed any topic, I will also include that in this article.


You Might Also Like

0 comments

Follow by Email