Glossary / Event emitters

Event emitters

Node.js EventEmitter is a synchronous in-process publish/subscribe mechanism: emitters invoke registered listeners for a named event, passing supplied arguments. It is useful for lifecycle notifications and decoupling components, but it is not a message queue, does not inherently provide backpressure, and listeners run on the emitting call stack.

Registration and invocation semantics

on() registers a listener each time it is called; registering the same function repeatedly creates repeated calls. once() removes its listener before or during its first invocation. removeListener()/off() removes a matching registration. Listeners are invoked synchronously in registration order for that emission, so a slow listener blocks the emitter and exceptions can escape from emit().

import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
bus.once('ready', value => console.log(value));
bus.emit('ready', 42); // prints 42 synchronously

The special error event has process-level significance: emitting it without a registered listener throws the error and can terminate the process. EventEmitter does not automatically await promises returned by async listeners. Rejections may become unhandled; use explicit try/catch, a wrapper, or carefully consider captureRejections and its behavior.

Design and failure modes

Events reduce direct coupling but can hide control flow, ownership, and ordering. A listener added after an event has fired does not receive history. There is no built-in replay, durability, schema validation, listener isolation, or queue bound. maxListeners is a leak warning threshold, not a hard cap. Remove listeners when their owner ends, especially for long-lived emitters.

Do not emit an event and assume every observer succeeded. If all handlers must complete or their errors must affect the operation, use explicit function calls or a defined async orchestration API. Event names and payloads should be stable and documented; avoid exposing mutable payload objects that listeners can unexpectedly change.

Interview questions

  1. Are listeners invoked asynchronously? No, standard EventEmitter listeners run synchronously during emit().
  2. What is special about error? Without an error listener, emission throws and may terminate the process.
  3. Does emit await async listeners? No; returned promises are not awaited automatically.
  4. Why can duplicate registration be a bug? Each registration results in another invocation and may retain closures.
  5. When prefer direct calls? When completion, return values, or error propagation across participants must be explicit.

References