Async Patterns, Errors and EventEmitter
Node layers its own async conventions on top of promises: error-first callbacks, synchronous EventEmitters, process lifecycle events, AbortSignal-aware APIs and AsyncLocalStorage. Know how each behaves when things go wrong, because that is what crashes production services.
Key points
- 1
Error-first callbacks must be called exactly once and always asynchronously. Mixing sync and async callbacks ("Zalgo") makes ordering unpredictable; defer cache hits with
process.nextTick. - 2
util.promisifyresolves with the first result only, forwards its ownthis(so bind methods), and returnsfn[util.promisify.custom]when present, which is whypromisify(setTimeout)is thetimers/promisesversion. - 3
emit()runs listeners synchronously in registration order on a snapshot of the listener list. A throwing listener stops the rest, anderrorwith no listener throws. Async listeners' rejections are ignored unlesscaptureRejectionsis on. - 4
events.oncerejects iferrorfires first, andevents.onyields forever until aborted. Neither keeps the process alive, and neither does a timeout signal: only ref'd handles (sockets, timers, I/O) do. - 5
Since Node 15 an unhandled rejection crashes the process (
--unhandled-rejections=throw) unless anunhandledRejectionlistener exists.strictcrashes even with one, andwarn-with-error-codecontinues but exits with code 1. - 6
After
uncaughtException, log, clean up synchronously, exit non-zero and let a supervisor restart you.exitlisteners cannot do async work;beforeExitfires only when the loop drains naturally, not onprocess.exit(). - 7
AsyncLocalStorage context follows the async resource that runs a callback. Libraries that queue callbacks (pools, batchers) must wrap them with
AsyncResource.bind, or requests see each other's store.
Common traps
Removing a listener during
emit()does not stop it running in that same emit; the change applies to the next one.A plain
setTimeoutbackoff ignores cancellation. Usetimers/promisessetTimeout(ms, undefined, { signal })so an abort ends the wait immediately.An entry ES module whose top-level
awaitcan never settle exits with code 13, often because an event was emitted before anyone awaited it.
Test yourself on Async Patterns, Errors and EventEmitter
Ten questions, with the answer and explanation after each one.