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.
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.
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.
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!
You can turn off the listener using the emitter.off
or emitter.removeListener
methods.
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
You can turn off all listeners using the emitter.removeAllListeners
method. The event handlers will be garbage collected, and the memory will be freed.
1// removes all event listeners for the event
2emitter.removeAllListeners('greet');
3emitter.emit('greet'); // No output, as all handlers have been removed
emitter.once
registers a one-time listener for the event.
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');
Event emitters are synchronous. Callbacks are executed immediately once the event is emitted.
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.
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
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');
1// Event listener added
2// Event emitted
3// Hello, Alice!
EventEmitter's are not available in the browser, you can create your own custom EventEmitter
or use popular opensource libraries like Mitt.
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.