Event Emitters
Node.js’s event-driven, non-blocking asynchronous architecture relies heavily on the idea of event emitters. Node.js applications can effectively manage a large number of concurrent connections thanks to its design, which eliminates the complications that come with multi-threading.
The Core Module and Class
A large portion of Node.js’s core API is constructed using an idiomatic asynchronous event-driven architecture. The events
module makes this possible by offering the EventEmitter
class. You can use the EventEmitter
to construct an observer pattern in which certain objects, referred to as “emitters,” periodically release events with names. Then, other objects, referred to as “listeners,” can be registered to respond to these occurrences by carrying out particular actions.
EventEmitter
is an extensible object that offers a toolbox for creating secure asynchronous interfaces to object methods. If you’re using core timer functions, you usually don’t need to specify require('events')
because this class is a global entity in Node.js and the functions are universally available. But typically, you need to require('events')
in order to utilise the EventEmitter
class directly.
The Publish-Subscribe (Pub-Sub) Pattern
Node.js Event Emitters are built on top of the publish-subscribe (pub-sub) architecture. Within this pattern:
- Publishers, also known as event emitters, send out messages (events) without knowing any personal information about their subscribers.
- When subscribers (listeners) receive these occurrences, they respond to them in unique ways.
The fact that this pattern separates the triggering action from the subsequent actions gives it a major edge over more conventional asynchronous programming strategies like layering callbacks or chaining promises. Applications become more modular, readable, and maintainable as a result of being able to modify how subscribers respond to events without changing the publisher’s code.
Creating and Using Custom Events
An EventEmitter
instance is required before you can use Event Emitters. This can be accomplished in two typical ways:
- Creating a standalone
EventEmitter
object:const myEmitter = new events.EventEmitter();
. This is appropriate when events arise from activities across several objects or are unrelated to your business objects. - Extending the
EventEmitter
class: Creating a class thatextends EventEmitter
is the first step in the process. This allows for easy access to EventEmitter’s methods, which is frequently preferable when the events are a direct result of an object’s actions.
Example:
const EventEmitter = require('events');
// Define a class that extends EventEmitter
class Dog extends EventEmitter {
constructor(name) {
super(); // Call the parent constructor
this.name = name;
}
chew(item) {
console.log(`${this.name} is chewing on ${item}.`);
this.emit('chew', item); // Emit the 'chew' event
}
bark() {
console.log(`${this.name} barks!`);
this.emit('bark'); // Emit the 'bark' event
}
}
// Create an instance of Dog
let myDog = new Dog('Bluey');
// Listen for the 'chew' event
myDog.on('chew', (item) => {
if (item === 'shoe') {
console.log('Time to buy another shoe, you rascal!');
} else {
console.log('Good dog!');
}
});
// Listen for the 'bark' event
myDog.on('bark', () => {
console.log('WHO\'S AT THE DOOR, WHO WHO?');
});
myDog.chew('shoe'); // Call the method that emits the event
myDog.chew('bone');
myDog.bark();
Output:
Bluey is chewing on shoe. Time to buy another shoe, you rascal! Bluey is chewing on bone. Good dog! Bluey barks! WHO'S AT THE DOOR, WHO WHO?
Registering Listeners
The on()
method can be used to register functions to listen for designated events after you have an instance of EventEmitter
. The on()
function requires two inputs:
- The name of the event (a string).
- A callback function will be run upon the event’s initiation.
To accomplish the same thing, Node.js also offers addListener()
as an alias for on()
. In the event that more than one listener is added for the same event, they will all be called simultaneously in the order that they registered.
Example:
// Import events module
const EventEmitter = require('events');
// Create an eventEmitter object
const myEmitter = new EventEmitter();
// Create an event handler (listener)
const connectionHandler = function connected() {
console.log('Connection successful, mate!');
// Fire the 'data_received' event
myEmitter.emit('data_received');
};
// Bind the 'connection' event with the connectionHandler
myEmitter.on('connection', connectionHandler);
// Bind the 'data_received' event with an anonymous function
myEmitter.on('data_received', function() {
console.log('Data received successfully, fair dinkum!');
});
// You can pass arguments to the event handler by passing them as additional arguments to emit()
myEmitter.on('start', (number) => {
console.log(`Started with number: ${number}`);
});
// Emit (fire) the 'start' event with a number
myEmitter.emit('start', 23);
// Emit (fire) the 'connection' event
myEmitter.emit('connection');
console.log("Program ended.");
Output:
Started with number: 23 Connection successful, mate! Data received successfully, fair dinkum! Program ended.
Triggering Events
To trigger events, use the emit()
method. The first argument supplied to this method is the event name. Any number of other arguments can then be passed as data to the callback methods of the registered listeners. MyEmitter.emit('buy', email, price, Date.now());
would send email
, price
, and timestamp
data to the buy
event listeners. The emit()
function returns true
if there were listeners for the event, and false
otherwise.
One-Time Listeners with
Instead of using on()
, you can use the once()
function if you want a listener to respond to an event just once. Once a once()
listener has received and emitted the event, Node.js automatically removes the listener.
Example:
const EventEmitter = require('events');
const ticketManager = new EventEmitter();
ticketManager.on('buy', (email, price) => {
console.log(`Someone bought a ticket! Email: ${email}, Price: $${price}`);
});
// Add a one-time event listener for 'buy'
ticketManager.once('buy', () => {
console.log("This is only called once, cobber!");
});
// Buy a ticket – this will trigger both listeners (the 'on' and the 'once')
ticketManager.emit('buy', 'test@example.com', 20);
// Buy another ticket – this will only trigger the 'on' listener, as 'once' listener was removed
ticketManager.emit('buy', 'another@example.com', 25);
Output:
Someone bought a ticket! Email: test@example.com, Price: $20 This is only called once, cobber! Someone bought a ticket! Email: another@example.com, Price: $25
Managing Listeners: , , and
Methods to manage subscribers are offered by event emitters:
off()
/removeListener()
:The functions off() and removeListener() eliminate a particular listener from an event. A reference to the precise callback function that was registered is required in order to use them.removeAllListeners()
: This function deletes all listeners for a given event, or in the absence of an event name, deletes all listeners for all emitter events. It is generally recommended to exercise caution when deleting listeners that were not established in the same scope, particularly for essential Node.js objects like file streams or sockets.
You may also use emitter.listenerCount(eventName)
to keep track of how many people are listening to a particular event, or emitter.eventNames()
to get an array of registered event names.
Handling Error Events
Managing mistakes is a key component of Event Emitters. When an EventEmitter
runs into a scenario that prevents it from carrying out its function, it need to generate a 'error'
event. Always keeping an ear out for 'error'
events is regarded as best practice. An 'error'
event will cause Node.js to throw an error and crash the application if no listener is attached. Application crashes can be avoided by gracefully managing exceptions via an error listener.
Example Code
const EventEmitter = require('events');
class TicketManager extends EventEmitter {
constructor(supply) {
super();
this.supply = supply;
}
buy(email, price) {
if (this.supply > 0) {
this.supply--;
this.emit("buy", email, price, Date.now());
console.log(`Ticket bought by ${email}. Remaining tickets: ${this.supply}`);
return;
}
// Emit an error event if no tickets are left
this.emit("error", new Error("There are no more tickets left to purchase, mate."));
}
}
const ticketManager = new TicketManager(3); // Start with 3 tickets
// **Crucial: Always set up an error listener for Event Emitters!**
ticketManager.on("error", (error) => {
console.error(`Gracefully handling our error: ${error.message}, no worries.`);
});
// Listener for successful purchases
ticketManager.on("buy", (email, price, timestamp) => {
console.log(`Successfully processed purchase for ${email} at $${price} on ${new Date(timestamp)}`);
});
// Try to buy more tickets than available
ticketManager.buy('user1@example.com', 10);
ticketManager.buy('user2@example.com', 10);
ticketManager.buy('user3@example.com', 10);
ticketManager.buy('user4@example.com', 10); // This will emit an error
Output:
Successfully processed purchase for user1@example.com at $10 on Tue Jul 22 2025 11:22:17 GMT+0000 (Coordinated Universal Time) Ticket bought by user1@example.com. Remaining tickets: 2 Successfully processed purchase for user2@example.com at $10 on Tue Jul 22 2025 11:22:17 GMT+0000 (Coordinated Universal Time) Ticket bought by user2@example.com. Remaining tickets: 1 Successfully processed purchase for user3@example.com at $10 on Tue Jul 22 2025 11:22:17 GMT+0000 (Coordinated Universal Time) Ticket bought by user3@example.com. Remaining tickets: 0 Gracefully handling our error: There are no more tickets left to purchase, mate., no worries.
Consider Event Emitters as a type of public address system in a large building. The building manager (the emitter) simply broadcasts important announcements (an event) over the system, and various offices (the listeners) may have staff assigned specifically to respond to certain announcements (e.g., “Fire Drill,” “Lunch is Served,” “Visitor Arrived”). The manager does not need to know who is listening or what each office will do; they simply send the message. In the event of an emergency (an err