Events and EventEmitter
EventEmitter patterns for libraries, requests, and process signals. Results appear in the same fence: same-line // comments when short, multiline // blocks below the sample when not.
Search across all documentation pages
EventEmitter patterns for libraries, requests, and process signals. Results appear in the same fence: same-line // comments when short, multiline // blocks below the sample when not.
Register listeners with on/addListener and notify them with emit.
import { EventEmitter } from "node:events";
const bus = new EventEmitter();
const seen: string[] = [];
bus.on("msg", (text: string) => seen.push(text));
bus.emit("msg", "hello");
seen // ["hello"]events.once returns a promise for the next event - great with async/await.
import { once, EventEmitter } from "node:events";
const ee = new EventEmitter();
setTimeout(() => ee.emit("ready", 42), 0);
const [value] = await once(ee, "ready");
value // 42Emitters that emit error without a listener crash the process - always attach handling.
import { EventEmitter } from "node:events";
const ee = new EventEmitter();
const errs: string[] = [];
ee.on("error", (err: Error) => errs.push(err.message));
ee.emit("error", new Error("boom"));
errs // ["boom"]Remove listeners on shutdown to avoid leaks - prefer named functions over anonymous.
import { EventEmitter } from "node:events";
const ee = new EventEmitter();
function onData(n: number) { return n; }
ee.on("data", onData);
ee.listenerCount("data") // 1
ee.off("data", onData);
ee.listenerCount("data") // 0Keep handlers fast or queue work - avoid blocking the event loop inside listeners.
import { EventEmitter } from "node:events";
const bus = new EventEmitter();
const jobs: number[] = [];
bus.on("job", (n: number) => queueMicrotask(() => jobs.push(n)));
bus.emit("job", 1);
await Promise.resolve();
jobs // [1]prependListener runs before listeners registered with on.
import { EventEmitter } from "node:events";
const ee = new EventEmitter();
const order: string[] = [];
ee.on("x", () => order.push("second"));
ee.prependListener("x", () => order.push("first"));
ee.emit("x");
order // ["first", "second"]Debug how many listeners are attached to an event name.
import { EventEmitter } from "node:events";
const ee = new EventEmitter();
ee.on("data", () => {});
ee.on("data", () => {});
ee.listenerCount("data") // 2
ee.eventNames() // ["data"]Raise the max listeners warning threshold only when the fan-out is intentional.
import { EventEmitter } from "node:events";
const ee = new EventEmitter();
ee.setMaxListeners(50);
ee.getMaxListeners() // 50Inspect wrappers including once wrappers during debugging.
import { EventEmitter } from "node:events";
const ee = new EventEmitter();
ee.once("ready", () => {});
ee.rawListeners("ready").length // 1Tie cancellation to emitters with AbortSignal event listeners.
const ac = new AbortController();
let canceled = false;
ac.signal.addEventListener("abort", () => { canceled = true; }, { once: true });
ac.abort();
canceled // true
ac.signal.aborted // trueSome Node APIs use Web EventTarget - same mental model, different method names.
const t = new EventTarget();
let n = 0;
t.addEventListener("ping", () => { n += 1; });
t.dispatchEvent(new Event("ping"));
n // 1Enable rejection capture so async listener failures become error events when configured.
import { EventEmitter } from "node:events";
EventEmitter.captureRejections = true;
EventEmitter.captureRejections // trueDomain objects often extend EventEmitter for lifecycle hooks.
import { EventEmitter } from "node:events";
class Job extends EventEmitter {
start() { this.emit("start", "go"); }
}
const job = new Job();
const seen: string[] = [];
job.on("start", (m: string) => seen.push(m));
job.start();
seen // ["go"]process.nextTick queues before other microtasks - use sparingly for fairness.
const order: string[] = [];
process.nextTick(() => order.push("tick"));
Promise.resolve().then(() => order.push("promise"));
await new Promise((r) => setImmediate(r));
order
// ["tick", "promise"]Handle graceful shutdown via process signal events.
// process.on("SIGINT", () => {
// shutdown().finally(() => process.exit(0));
// });
// emits when user hits Ctrl+C (when supported)Stack versions: Node.js 24.18.0 (LTS line 24) · TypeScript 5.6+ · npm 10+
Reviewed by Chris St. John·Last updated Jul 18, 2026