Events and EventEmitter
Why It Matters
Many Node.js APIs are event-driven. Streams emit data, end, and error. Servers emit connection events. Processes emit lifecycle events. Understanding EventEmitter helps you read Node.js code and design small event-based abstractions.
Core Concepts
EventEmitter lets an object publish named events and lets listeners subscribe.
import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
bus.on('user:created', (user) => {
console.log('Created user', user.id);
});
bus.emit('user:created', { id: 123 });Listeners run synchronously when emit is called. If a listener is slow, emit is slow.
Syntax and Examples
once
Use once when a listener should run only the first time:
bus.once('ready', () => {
console.log('Ready only once');
});Removing listeners
function onMessage(message) {
console.log(message);
}
bus.on('message', onMessage);
bus.off('message', onMessage);Remove listeners when they are tied to a request, socket, UI session, or other lifecycle. Otherwise long-lived emitters can retain memory.
Error events
error is special. If an emitter emits error and nobody listens, Node.js throws.
bus.on('error', (error) => {
console.error('Emitter failed:', error);
});
bus.emit('error', new Error('Something broke'));Use Cases
Use events when:
- One producer has multiple independent listeners.
- You are modeling lifecycle events.
- You are integrating with Node.js stream or server APIs.
- You want loose coupling inside a small boundary.
Avoid events when:
- A direct function call is clearer.
- The order of operations is critical and hard to trace.
- You need request-response behavior.
- Errors need structured propagation across layers.
Common Mistakes
- Forgetting
errorlisteners. - Assuming listeners run asynchronously.
- Creating hidden control flow that is hard to follow.
- Adding listeners repeatedly without removing them.
- Using a global event bus for unrelated application behavior.
Practical Challenge
Create a JobRunner class that extends EventEmitter. It should emit:
startbefore work beginssuccesswith a resultfailurewith an errorfinishin all cases
Then attach listeners and run a successful and failing job.
Recap
EventEmitter is a small but important Node.js primitive. Events are useful for lifecycle notifications and stream-like APIs, but listeners run synchronously and must be managed carefully.