Node.js Events

Node's EventEmitter class is the foundation of its event-driven architecture, letting objects emit named events that any number of listeners can react to.

The EventEmitter Class

Many of Node's core objects—HTTP servers, readable and writable streams, child processes—are built on top of the EventEmitter class from require('node:events'). Rather than returning a single value, these objects announce things as they happen by emitting named events, and any code that cares can subscribe to those events with a listener function. You can use EventEmitter directly by instantiating it, or extend it in your own classes to give them the same publish/subscribe behavior.

Registering and Emitting Events

The .on(eventName, listener) method registers a function to run every time that named event is emitted, and .emit(eventName, ...args) triggers the event, calling every registered listener synchronously, in the order they were added, and passing along any extra arguments. A single event can have any number of listeners, and none of them need to know about each other.

  • emitter.on(event, listener) — registers a listener that runs every time the event fires
  • emitter.once(event, listener) — registers a listener that runs at most one time, then removes itself
  • emitter.emit(event, ...args) — synchronously calls all listeners registered for that event
  • emitter.off(event, listener) (alias for removeListener) — unregisters a specific listener
  • emitter.listenerCount(event) — reports how many listeners are currently registered for an event

Registering multiple listeners for one event

const { EventEmitter } = require('node:events');

const emitter = new EventEmitter();

emitter.on('userLoggedIn', (username) => {
  console.log(`Welcome back, ${username}!`);
});

emitter.on('userLoggedIn', (username) => {
  console.log(`Audit log: ${username} logged in at ${new Date().toISOString()}`);
});

emitter.emit('userLoggedIn', 'alice');
// Welcome back, alice!
// Audit log: alice logged in at 2026-...

One-Time Listeners and Custom Emitters

Sometimes you only care about the first occurrence of an event—a 'ready' event fired once at startup, for example. once() covers that case: it behaves like on(), except the listener automatically detaches itself immediately after its first call, so calling emit() again for that event has no effect on it. When you no longer need a listener at all, off() (or its older alias removeListener()) removes it explicitly.

once() and removing listeners

const { EventEmitter } = require('node:events');

const emitter = new EventEmitter();

emitter.once('ready', () => console.log('Server initialized'));

emitter.emit('ready'); // Server initialized
emitter.emit('ready'); // (nothing happens — listener already removed)

function onWarning(msg) {
  console.log('Warning:', msg);
}

emitter.on('warning', onWarning);
emitter.emit('warning', 'low disk space'); // Warning: low disk space

emitter.off('warning', onWarning);
emitter.emit('warning', 'low disk space'); // (nothing happens — listener removed)
MethodPurpose
on(event, fn)Run fn every time event fires
once(event, fn)Run fn only the first time event fires
emit(event, ...args)Fire event synchronously, calling every listener
off(event, fn) / removeListener(event, fn)Detach a specific listener
listenerCount(event)Check how many listeners are registered

Extending EventEmitter in a custom class

const { EventEmitter } = require('node:events');

class Downloader extends EventEmitter {
  start(fileName) {
    this.emit('start', fileName);

    // Simulate progress without a real network call
    setTimeout(() => this.emit('progress', 50), 100);
    setTimeout(() => this.emit('complete', fileName), 200);
  }
}

const downloader = new Downloader();

downloader.on('start', (fileName) => console.log(`Starting download: ${fileName}`));
downloader.on('progress', (percent) => console.log(`Progress: ${percent}%`));
downloader.on('complete', (fileName) => console.log(`Finished: ${fileName}`));

downloader.start('report.pdf');
Note: emit() runs every listener synchronously and returns only once they've all finished. If a listener throws and there's no listener registered for the special 'error' event, an EventEmitter throws that error all the way up and can crash the process—so error-prone listeners should register an 'error' handler too.

Exercise: Node.js Events

In what order does an EventEmitter invoke multiple listeners registered for the same event?