CodePark/ questions / Behavioral patterns
START PRACTICINGPRACTICE
Backend question · Software design & patterns › Behavioral patterns

How does Node's EventEmitter implement the Observer pattern, and what pitfalls should you know when using it for in-process events?

orders.on("order.placed", (o) => mailer.sendConfirmation(o));
orders.on("order.placed", (o) => analytics.track(o));
orders.emit("order.placed", order);

EventEmitter is the Observer pattern. on() adds a listener for an event name. emit() calls every listener for that event name.

In the Observer pattern, a subject keeps a list of subscribers. When something happens, it notifies them. The subject doesn't depend on who reacts.

Pitfalls:

  • emit() calls listeners synchronously, in the order they were added. A slow listener blocks the emitter.
  • If a listener throws, emit() throws in the caller.
  • Async listeners return promises, and emit() ignores them. By default, the emitter does not handle their rejections.
  • If you emit "error" and no listener exists for it, Node throws.
  • Adding listeners again and again without removing them leaks memory. By default, Node warns when an event has more than 10 listeners.

Also, events live in memory only. If the process crashes, they are lost. For work that must happen, use a durable queue instead. A durable queue stores jobs on disk or in a separate service, so they survive a crash.

  • The subject notifies registered observers without knowing who they are.
  • Listeners run synchronously, in order. If one throws, the error comes out of emit().
  • By default, the emitter does not handle rejections from async listeners.
  • An "error" event with no listener throws. Too many listeners trigger a leak warning.
  • Events stay inside one process and are not durable.