Ch. 6 · Node.js

Node.js Events and Listener Ordering

Trace synchronous EventEmitter delivery, promise-based waiting and cleanup. Prevent missed events by registering listeners first.

~3 min readintermediateupdated Oct 3, 2026

An event can be lost because the listener was installed too late, even in code that looks sequential and uses await. EventEmitter delivery and promise continuation have different timing. Interview candidates should be able to trace both rather than assuming asynchronous-looking code makes every event asynchronous.

Before you start

Know callbacks, promises and the microtask queue. The runnable example uses Node.js EventEmitter and its promise-based once helper. It contains no server and does not need a network connection. Event names and payload shapes are an application contract.

Step-by-step walkthrough

Step 1: Observe synchronous dispatch

When emit runs, ordinary registered listeners run synchronously in their registration order. A listener can modify shared state before emit returns. Code after emit therefore sees those completed synchronous changes, while an async listener’s later awaited work has not necessarily finished.

Step 2: Register every required wait first

If a producer emits ready and done together, awaiting ready before installing the done listener can miss done. Register both promise waits before triggering the producer. This makes the required observation window explicit and prevents relying on a later promise continuation to install a listener in time.

Step 3: Own incomplete waiting

A wait that never receives its event can retain listeners indefinitely. Real operations need timeout or cancellation and an owner that observes rejection. For an application event source, also specify how failure is signalled; an error event and a normal completion event have different semantics.

Worked scenario

Save as events.mjs and run with Node.js.

import { EventEmitter, once } from 'node:events';
const bus = new EventEmitter();
const observed = [];
bus.on('ready', () => observed.push('listener'));
const ready = once(bus, 'ready');
const done = once(bus, 'done');
queueMicrotask(() => {
  observed.push('before emit');
  bus.emit('ready', 'connected');
  observed.push('after emit');
  bus.emit('done', 42);
});
console.log((await ready)[0]); // connected
console.log((await done)[0]); // 42
console.log(observed);
// ['before emit', 'listener', 'after emit']
JavaScript

The ready listener executes between the two producer-side array writes. Both promise waits already exist when the producer emits. Awaiting ready resumes after the producer’s synchronous work, so installing done only at that later point would be too late for this sequence.

Common mistake

Marking a listener async does not make emit wait for its promise. If a workflow must finish all consumers before reporting completion, model that as an explicit promise-based operation or another documented coordination mechanism. A warning about too many listeners is evidence to investigate ownership, not a reason to raise the limit automatically.

Verify the behavior

Execute the example and compare the event payloads and array order. For a missing-event case, use an AbortController with the once options supported by Node, abort it and assert rejection. Verify the wait’s listener is removed. Check duplicate emissions and clarify whether a caller needs the first event, every event or a latest-state snapshot.

Interview exercise

A task emits started and finished in one synchronous function. Why can two sequential awaits hang?

Answer and reasoning

The first wait resolves during started, but its await continuation runs later. The function can emit finished before that continuation registers another listener. Nothing will then resolve the second wait. Register both waits first, or use a task API that returns its completion promise directly. Events announce observations; they do not automatically preserve history for future subscribers.

Continue learning

Review Node.js async error ownership and Node.js interview questions. The Node events documentation defines delivery and once behavior.

More in Node.js

read ✓Node.js · hard

Node.js Clustering Across CPU Cores

Use cluster to run several workers on all cores, restart crashed workers, and understand shared-port and shared-state limits.

~2 min readread →
read ✓Node.js · hard

Node.js Password Hashing with scrypt

Store passwords as salted hashes with a slow key-derivation function, and compare candidates in constant time.

~2 min readread →
esc