Event Emitters and Event Targets
Overview
All modern applications work in an active environment. Data comes through user interaction. Let us take an example of a web browser. The user clicks on a button or any field, triggering an event. This is the case with all the GUI applications. User activity generates events that can be used to perform suitable actions. Hence we can connect all these events to drive the flow of the application. Event-Driven programming gives a lot of benefits to the developers. It provides a way to make simple applications with fewer system resources.
Introduction to Event Emitter in Node.js
Event-driven programming is a programming paradigm in which the program flow depends on the actions performed by the user. It helps avoid complexity and collision issues. Event-driven programming is based on the following concepts :
- Event Emitter that raises the happening of an event.
- Event Loop that listens for event triggers and calls the required event handler.
- Event Handler is a function that performs some actions for a particular event happening.
The core of NodeJS is based on event-driven architecture. Event-driven programming is one of the reasons behind the speed of NodeJS when compared to other technologies.
At the start of a NodeJS server, all the listeners and variables are initialized so that they can respond to the trigger of events.
Benefits of Event Emitters in Node.js
- Provides more flexibility in programming.
- Easier than any other type of programming.
- Provides better scalability and fault tolerance.
- It helps in providing asynchronous and reactive systems.
- It reduces CPU consumption and bandwidth usage.
- It allows excellent separation of responsibility without stopping components from working together.
How and Where Node.js Internally uses Event Emitters?
EventEmitters in Node.js are used widely across its environment. For example:
-
Streams in Node.js are extended from eventEmittors. Streams can raise events like data, open, and end.
Example : Suppose data.txt is a large file with random text.
Output :
Explanation :
In the above example, the listeners are called internally by Node.js by extending event emitters, exposing custom predefined events (end, open, and data), and raising these three events automatically (without explicitly using eventEmitters) when required for the streams.In the data listener, instead of string in the text file, the buffer is printed since Node.js, instead of reading the text content of the file, actually reads the file as a buffer.
-
Global process variable also uses event emitters nodejs internally. The process object helps in listening to and responding to some events accordingly. These events are:
Example :
Output :
Creating an Event Emitter in Node.js
EventEmitter is a class that helps in the creation of a Producer-Consumer pattern in Node.js.
In Node.js, the EventEmitter class is exported by the events module. We can attach a listener to an event using different methods provided by EventEmitter class and then trigger that event using the emit() method.
The emitter object has two key characteristics:
- Emitting name events: It is the signal denoting any kind of status change in the object.
- Registering and Unregistering listener functions: The binding of callback functions to the events.
Syntax :
Importing event emitter nodejs module :
Creating an eventEmitter object/instance :
Listening to a custom event :
Triggering a custom event :
Now combining all of the above, let's look at the following example to understand the basic structure of Event emitters in Node.js.
Example :
Create a javascript file named index.js with the following code :−
Now run the index.js file
Output :
All the function used above are explained in detail throughout the article.
Publishing Events and Listening to Them
The event emitter nodejs class comes with a lot of member functions.
A few of these methods are:
Methods by eventEmittor in Node.js | Description |
---|---|
emitter.once(event, listener) | This function adds a one-time listener for an event at the end of the listener array. Even if the event is emitted multiple times, the listener is executed only once. |
emitter.removeAllListeners([event]) | Removes all the listeners if no arguments are passed or the listeners of the events passed as argument. |
emitter.setMaxListeners(n) | It prints a warning if more than n listeners are added for a particular event. |
emitter.getMaxListeners() | It returns the value of the number of maximum listeners set by setMaxListeners or the value of EventEmitter.defaultMaxListeners. |
emitter.listeners(event) | Returns the array of listeners for a particular event. |
emitter.listenerCount(type) | Returns the count of listeners bound to a particular event. |
emitter.prependListener(eventName, listener) | It adds the listener to the beginning of the listener's array of the event without making any checks for if the listener has already been added or not. |
emitter.prependOnceListener(eventName, listener) | Adds a one-time listener at the beginning of the listener's array of the event. |
Registering for the Event to be Fired Only One Time using once()
When a listener is registered using the eventEmitter.on(eventName) or eventEmitter.addListener(eventName) methods, that listener is invoked every time that event is fired upon.
Sometimes you want your application to listen to an event only once; that is the first time that event has occurred. To help with this case, Node.js provides us with the once() method, which only responds to the event when it occurred for the first time.
Syntax :
- eventName: The name of the event (String | Symbol)
- listener: The callback function
- Returns: The reference to the EventEmitter so that calls can be chained.
Example without using once()
Notice that the event to increment val is emitted twice, and the value of val is incremented twice.
Example using once()
Notice that the event to increment val is emitted twice, but running this code yields the same value of val both times because the once() method was used.
Registering for the Event with Callback Parameters
We can register a callback with the events while firing an event. These callbacks can be a single statement or any object etc.
Syntax :
Example :
Output :
Data fetched from the API with status Ok and code 200
Listening Events
Events can be listened to by using the method eventEmitter.on() or eventEmitter.addListener().
The on method is used to bind callback functions to the events. It returns the Event Emitter nodejs instance to facilitate the chaining of methods. It adds the listener to the end of the listener's array of events without making any checks for if the listener has already been added or not. In other words, if we use one function with the same event and listener function multiple times, the listener will be executed multiple times.
Syntax :
- eventName: The name of the event (String | Symbol)
- listener: The callback function
- Returns: The reference to the EventEmitter so that calls can be chained.
Example :
Note: The addListener checks if the event is already registered. If yes, it returns the listener array. Otherwise it returns an empty array.
Emitting Events
The emit() method is used to raise specified events with the supplied arguments. All the registered event listeners are called synchronously. It returns true if any listener function is bound to the event. Otherwise it returns false.
Syntax :
- eventName: The name of the event (String | Symbol)
- listener: any arguments
- Returns: The true or false.
Example :
Output :
Removing Listener
Events can be removed by using the method eventEmitter.removeListener().
It grabs the listener's array of events and loops through all the listeners.
If the current listener matches the particular event, then removeListener() removes the particular event from the array and returns. If none is found matching, then removeListener() will just return the listener's array without any modification.
(In case of multiple listeners, others will not be impacted)
Syntax :
- eventName: The name of the event (String | Symbol)
- listener: The callback function
- Returns: The reference to the EventEmitter so that calls can be chained.
Example :
Output :
Special Events
All eventEmitter in Node.js emit special events like, newListener, removelistener, and error.
-
newListener event: The instance of event emitter nodejs will emit its own newListener even before a listener is added to the internal listener array when an event is emitted. The event newListener is triggered before adding the listener to the array.
Syntax :
-
removeListener event: The instance of eventEmitter will emit a removeListener event after a listener is removed.
Syntax :
-
error event: This event is emitted when an error occurs within the event emitter nodejs instance. If no listener is registered to capture the error event, and an error event is emitted, Node.js throws an error, and the stack trace is printed after the Node.js process exists.
Syntax :
Example :
Unregistering Events
Events can be removed by using the method eventEmitter.off(), so when that event is emittted nothing happens.
Syntax :
- eventName: The name of the event (String | Symbol)
- listener: The callback function
- Returns: The reference to the EventEmitter so that calls can be chained.
Syntax :
Example :
Output :
Maintaining a Single Event-Emitter Instance Applicationwide
A Node.js project generally consists of 100's of files. Thus making it difficult to maintain a single copy of the eventEmitter instance throughout the Node.js project. There is a strategy to create and maintain a singleton copy for an EventEmitter instance. When creating the EventEmitter instance, we can simply store the Event Emitter nodejs instance as an application-level setting using the app.set(<key>, <value>).
Example :
Is an Event Emitter in Nodejs Synchronous or Asynchronous?
All the listeners are called in the order in which they are registered, that is, synchronously. We can understand the synchronous nature of Event Emitter in Node.js from the example below:
Output : first second third
Explanation : From the above code, to send an event to all listeners, the listener's array is traversed and the callback of each listener is called in the sequence of the traversal.
Hence it is clear that all of the above functions are executed in the same order in which event listeners are bound.
The event emitter nodejs calls all listeners synchronously in the order in which they were registered. This is to avoid race conditions or logic errors and to ensure the proper sequencing of events.
We can observe the synchronous nature more clearly using one more example :
Output : RangeError: Maximum call stack size exceeded
Explanation : The event emitter nodejs synchronously executes all callbacks. Every time you call a function, its context is pushed to the top of the call stack. When the function ends, the context is taken off the stack. If you call one function inside the other, the data can pile up in your call stack and eventually cause it to overflow.
However, you can perform asynchronous calls by using process.nextTick() or setImmediate().
Example :
Output :
Order of Execution of the Listeners
The execution order of Listeners depends on the order in which listeners are created for an event emitter in Node.js
Output :
The vast majority of browser implementations (Chrome, Firefox, Opera, etc.), fire the handlers in the order in which they were attached. IE8 and earlier execute the listeners the other way around. Hence the order of execution is browser dependent.
Conclusion
- Event Emitter Nodejs is a class that helps in the creation of Producer-Consumer patterns in Node.js.
- Events are used internally in Streams and global process variables.
- Node.js provides us with the once() method, which only executes for the first time.
- Important EventEmitter methods are addListener() to add, removeListener() to remove, emit() for triggering an event and once() for only once execution of a event etc.
- There are special events like newListener(), removeListener() and error.
- EventEmitter class allows us to register multiple listeners for the same event.
- EventEmitter instance can be used at the application level using the app.set(<key>, <value>).
- All the listeners are called according to the order in which they are registered, that is, synchronously.
- However, you can perform asynchronous calls by using process.nextTick() or setImmediate().
- Chrome, Firefox, and Opera browsers execute the listeners in the order they are attached, while in older versions, execution is browser dependent.