|$ curl https://forge-ai.dev/api/markdown?path=docs/nodejs/events
$cat docs/eventemitter.md
updated Recently·20 min read·published

EventEmitter

Node.jsEventsIntermediate🎯Free Tools
Introduction

Event-driven programming is central to Node.js. The EventEmitter class from the events module is the mechanism behind streams, HTTP servers, sockets, and many third-party libraries. It lets objects emit named events and register listeners that respond to them.

While callbacks and promises handle one-off asynchronous operations, EventEmitters are ideal for ongoing, multi-step processes where multiple parts of the application need to react to state changes.

This guide explains the EventEmitter API, listener management, error handling, memory leak detection, and common design patterns for building maintainable event-based systems.

Basic Usage

Create an EventEmitter instance, register listeners with on or addListener, and emit events with emit. Listeners are called synchronously in the order they were added.

basic-emitter.js
JavaScript
1const EventEmitter = require('events');
2
3const emitter = new EventEmitter();
4
5emitter.on('ping', (data) => {
6 console.log('ping received:', data);
7});
8
9emitter.emit('ping', { time: Date.now() });
10
11// Output:
12// ping received: { time: 1234567890 }

The once method registers a listener that is removed after the first invocation. This is useful for one-time lifecycle events like ready or open.

once-listener.js
JavaScript
1emitter.once('ready', () => {
2 console.log('System is ready');
3});
4
5emitter.emit('ready');
6emitter.emit('ready'); // second emit does nothing

info

Use once for initialization events. It avoids the need to manually remove the listener and prevents duplicate setup logic.
EventEmitter as a Class Mixin

Most real-world usage extends EventEmitter in a custom class. This lets you combine event emission with business logic and state. Remember to call super() in the constructor.

class-emitter.js
JavaScript
1const EventEmitter = require('events');
2
3class JobQueue extends EventEmitter {
4 constructor() {
5 super();
6 this.jobs = [];
7 this.running = false;
8 }
9
10 add(job) {
11 this.jobs.push(job);
12 this.emit('job:added', job);
13 this.process();
14 }
15
16 async process() {
17 if (this.running) return;
18 this.running = true;
19
20 while (this.jobs.length > 0) {
21 const job = this.jobs.shift();
22 this.emit('job:started', job);
23 try {
24 await job.run();
25 this.emit('job:completed', job);
26 } catch (err) {
27 this.emit('job:failed', job, err);
28 }
29 }
30
31 this.running = false;
32 this.emit('idle');
33 }
34}

best practice

Document every event your class emits, including argument names and error conditions. This is the public API of your event-driven component.
Error Handling

If an EventEmitter emits an error event and there are no listeners for it, Node.js throws the error and may crash the process. Always attach an error listener.

emitter-error.js
JavaScript
1const EventEmitter = require('events');
2const emitter = new EventEmitter();
3
4emitter.on('error', (err) => {
5 console.error('Emitter error:', err.message);
6});
7
8emitter.emit('error', new Error('Something went wrong'));
9// Process stays alive

For application-level error handling, consider using an error event as the channel for non-fatal failures. Fatal errors should still propagate to a global handler or crash the process after cleanup.

warning

Never ignore error events from streams, servers, or third-party EventEmitters. An unhandled error event is one of the most common causes of production crashes.
Memory Leaks and Listener Limits

Adding listeners without removing them keeps emitter objects alive. This is especially dangerous when emitters are long-lived and listeners are registered repeatedly, such as inside request handlers.

listener-leak-bad.js
JavaScript
1// BAD: adds a new listener on every request
2app.get('/data', (req, res) => {
3 const emitter = getGlobalEmitter();
4 emitter.on('update', (data) => res.json(data));
5});
listener-leak-good.js
JavaScript
1// GOOD: remove listener when response finishes
2app.get('/data', (req, res) => {
3 const emitter = getGlobalEmitter();
4
5 function onUpdate(data) {
6 res.json(data);
7 emitter.off('update', onUpdate);
8 }
9
10 emitter.on('update', onUpdate);
11 req.on('close', () => emitter.off('update', onUpdate));
12});

EventEmitter warns when more than 10 listeners are attached to a single event. You can change this with setMaxListeners, but a high number usually indicates a leak.

best practice

Use once for events that happen at most once per listener lifecycle. Always pair on with a corresponding off or rely on component cleanup hooks.
Event-Driven Patterns

Request/Response correlation

When emitting a request event, include a correlation ID so the response can be matched to the original caller.

correlation.js
JavaScript
1const id = crypto.randomUUID();
2emitter.once(`response:${id}`, (result) => {
3 console.log('Got response', result);
4});
5emitter.emit('request', { id, payload });

Event namespaces

Use colon-separated namespaces for clarity: user:created, user:updated, user:deleted.

Async listeners

EventEmitter listeners are synchronous by design. If you need async handling, either await inside the listener or emit a promise-returning function.

📝

note

Avoid emitting inside a listener if the event is the same event being handled. Recursive emission can cause stack overflow or infinite loops.
events.once Promise Helper

The events module provides once(emitter, event), which returns a promise that resolves the next time the event fires. This bridges EventEmitters and promise-based code.

events-once.js
JavaScript
1const { once } = require('events');
2const http = require('http');
3
4const server = http.createServer();
5server.listen(3000);
6
7await once(server, 'listening');
8console.log('Server is listening');

info

Use events.once to integrate EventEmitters into async functions. It handles the listener removal automatically.
Common Mistakes
MistakeConsequenceFix
No error listenerProcess crashAlways handle error events
Leaked listenersMemory growthUse once and remove listeners
Synchronous emit assumptionsUnexpected orderingDocument listener execution order
Too many eventsHard to debugUse namespaces and typed schemas
$Blueprint — Engineering Documentation·Section ID: NODE-05·Revision: 1.0