Event Emitters:
Decoupling JavaScript with Pub/Sub

Published on: April 17, 2025

The Observer pattern is widely used in Object-Oriented Programming to enable objects to communicate without tight coupling. Event emitters and listeners play a key role in making this possible.

Introducing EventEmitter

The EventEmitter class is a built-in Node.js class that allows you to create objects that can emit and listen for events.

EventEmitter is not available in the browser. See EventEmitter on the browser section for more details.

javascript
1import { EventEmitter } from 'events';
2
3const emitter = new EventEmitter();
4
5const greetHandler = (name) => {
6	console.log(`Hello, ${name}!`);
7};
8
9emitter.on('greet', greetHandler);
10emitter.emit('greet', 'Alice'); // Output: Hello, Alice!
11emitter.emit('greet', 'Wonderland'); // Hello, Wonderland!

Here, emitter.on is used to register a listener for the greet event. When the event is emitted using emitter.emit('greet'), the callback function greetHandler is called.

Sending multiple arguments to callback

javascript
1const greetHandler = (...args) => {
2	for (const arg of args) {
3		console.log(`Hello, ${arg}!`);
4	}
5};
6
7emitter.on('greet', greetHandler);
8emitter.emit('greet', 'Alice', 'Mike', 'Bob');
9
10// Output:
11// Hello, Alice!
12// Hello, Mike!
13// Hello, Bob!

Deregistering the specified listener

You can turn off the listener using the emitter.off or emitter.removeListener methods.

javascript
1const greetHandler = () => {
2	console.log("Handler - 1");
3};
4const greetHandler2 = () => {
5	console.log("Handler - 2");
6}
7
8emitter.on('greet', greetHandler);
9emitter.on('greet', greetHandler2);
10emitter.emit('greet');
11// Output:
12// Handler - 1
13// Handler - 2
14
15// removes the second handler
16emitter.off('greet', greetHandler2);
17emitter.emit('greet');
18// Output:
19// Handler - 1

Deregistering all listeners

You can turn off all listeners using the emitter.removeAllListeners method. The event handlers will be garbage collected, and the memory will be freed.

javascript
1// removes all event listeners for the event
2emitter.removeAllListeners('greet');
3emitter.emit('greet'); // No output, as all handlers have been removed

Automatically deregistering the listener after first call

emitter.once registers a one-time listener for the event.

javascript
1emitter.once('greet', greetHandler);
2emitter.emit('greet', 'Alice'); // Hello, Alice!
3
4// No output, as the handler has been removed after the first call
5emitter.emit('greet', 'Wonderland');

EventEmitter is synchronous

Event emitters are synchronous. Callbacks are executed immediately once the event is emitted.

javascript
1console.log('Event listener added'); // Output: Event listener added
2emitter.emit('greet', 'Alice'); // Output: Hello, Alice!
3console.log('Event emitted'); // Output: Event emitted

As you can see, the console.log statements are executed in the same order they are defined.

setImmediate to the rescue

You can use setImmediate to ensure that the callback is executed in the next iteration of the event loop.

You can learn more about the event loop in my blog post.
Inside the JavaScript Event Loop: The Engine Behind Asynchronous Execution

javascript
1const greetHandler = name => {
2	setImmediate(() => console.log(`Hello, ${name}!`));
3};
4
5emitter.on('greet', greetHandler);
6
7console.log('Event listener added');
8emitter.emit('greet', 'Alice'); // will be processed asynchronously
9console.log('Event emitted');
javascript
1// Event listener added
2// Event emitted
3// Hello, Alice!

EventEmitter on the browser

EventEmitter's are not available in the browser, you can create your own custom EventEmitter or use popular opensource libraries like Mitt.

Conclusion

Event emitters are a powerful way to decouple your code and enable communication between objects.

They are widely used in backedend design patterns like the Observer pattern and are essential for building large-scale applications.

In this article