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.
This will create an EventEmitter instance. But what to do with it? Let's see it in action.
For now, we will only pay attention to these two member functions:
To publish an event, we use the emit() function and to listen to an event, we use the on() function.
In the last code snippet, where we created an EventEmitter object, we use the following code to raise events and listen to them.
Running the above code snippet prints the following output in the console:
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.
Output:
We get only Listener 1 as output in the console as the Listener 2 was registered after emitting the event.
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.
When we execute this code snippet, we get the following output in the console
The events raised by Event Emitters are synchronously executed by the listeners in the current Event Loop’s iteration.
When executed the above code gives the output:
The listener that is registered earlier, is executed earlier.
Streams are built on top of Event Emitters that raise pre-defined events like ‘open’, ‘end’, ‘data’, etc.
When executed, the code will give the following output:
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.
Another example of NodeJs using Event Emitters is the global process variable.
Following is the example showing their usage:
Running the above code will print the following output in the console
We will cover some of the useful member functions among them.
Running the above code will give the output:
Notice here the once listener was only called once. After getting called for the first time, it will be discarded for further use.
Running the following code will give the output:
Notice after the once event gets triggered, calling the eventNames() does not gives its event name.
Running the above code will gove the output as:
Running the following code will give the output as an empty array
- 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.
0 comments