EventEmitter

Emit named events and register listeners for them; the base class most Node.js APIs are built on.

Signature
new EventEmitter([options])
  • Added in Node.js v0.1.26

Parameters

ParameterTypeDescription
$optionsoptional<Object>Constructor options.
$options.captureRejectionsoptional<boolean>= falseEnables automatic capturing of promise rejections thrown by async listeners, routing them to the emitter's 'error' event.

Return value

EventEmitterA new emitter instance with an empty listener registry.

Errors

Emitting an 'error' event on an emitter that has no 'error' listener throws the emitted error, which crashes the process if nothing catches it.

Changelog

  • PHP v13.4.0, v12.16.0Added the captureRejections option. Without it, a rejected promise returned by an async listener becomes an unhandled rejection instead of an 'error' event.

EventEmitter is the synchronous publish/subscribe primitive that the rest of Node.js is layered on. Streams, HTTP servers, sockets, child processes and process itself are all emitters, so learning this one class explains the shape of most core APIs.

When to reach for it

Use it when one part of your program produces occurrences that zero or more unrelated parts care about, and the producer must not know who they are. A parser announcing rows, a job runner announcing progress, a cache announcing evictions. If exactly one consumer exists and it needs a value back, a plain callback or a promise is simpler and gives you error propagation for free.

What trips people up

Emission is synchronous. emit() runs every listener to completion before returning; it does not queue anything on the event loop. Listeners cannot return values to the emitter, and an async listener's rejection escapes entirely unless you pass captureRejections: true.

The 'error' event is special. An emitter with no 'error' listener throws the emitted error rather than dropping it, which is the usual cause of an unexplained crash from a long-lived emitter.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Give a long-running import job 'row', 'progress' and 'done' events so the CLI and the logger can both subscribe
  • Build an in-process message bus so a module can announce 'user:created' without importing the mailer
  • Model a connection object that signals 'ready', 'data' and 'close' to whoever is holding it

Frequently asked questions

Is EventEmitter synchronous or asynchronous?

It is synchronous: emit() calls every listener in registration order and only returns after the last one finishes. Nothing is scheduled on the event loop, so a slow listener blocks the emitter's caller. If you need async behaviour, wrap the emit in setImmediate() or have the listener itself do the async work.

How do I create a custom event emitter in Node.js?

Extend the class: `class Job extends EventEmitter {}`, then call `this.emit('name', payload)` from your methods. Consumers subscribe with `job.on('name', handler)`. You can also compose instead of inheriting by holding a plain `new EventEmitter()` as a private field and exposing on/off wrappers.

Why does my Node process crash when an event fires?

Almost always because an 'error' event was emitted with no 'error' listener attached — EventEmitter throws the error in that case by design. Attach `emitter.on('error', ...)` to every emitter you keep around, including streams and sockets.

More examples

Always handle the 'error' event

The first emitter has no 'error' listener, so emit() rethrows the error synchronously — in real code that is an uncaught exception and the process exits. The second emitter absorbs it and emit() returns true, exactly like any other event with a listener.

Example · javascript
const { EventEmitter } = require('node:events');

const unsafe = new EventEmitter();
try {
  unsafe.emit('error', new Error('boom'));
} catch (err) {
  console.log('emit threw:', err.message);
}

const safe = new EventEmitter();
safe.on('error', (err) => console.log('handled:', err.message));
console.log('emit returned:', safe.emit('error', new Error('boom')));

captureRejections for async listeners

captureRejections tells the emitter to attach a .catch() to any promise a listener returns and forward the rejection to the 'error' event. Output is "captured from async listener: handler failed". Note that emit() still returns immediately — it does not wait for the async listener.

Example · javascript
const { EventEmitter } = require('node:events');

// Without the option, this rejection becomes an unhandled rejection
// and the process exits on Node 15+.
const emitter = new EventEmitter({ captureRejections: true });

emitter.on('request', async () => {
  throw new Error('handler failed');
});

emitter.on('error', (err) => {
  console.log('captured from async listener:', err.message);
});

emitter.emit('request');

Node core objects are emitters too

http.Server, net.Socket, streams and process all extend EventEmitter, so 'request', 'listening' and 'error' are ordinary events registered with on(). Recognising this means the same removal, counting and once() rules from this page apply everywhere in core.

Example · javascript
const http = require('node:http');
const { EventEmitter } = require('node:events');

const server = http.createServer();
console.log('server is an emitter:', server instanceof EventEmitter);

server.on('request', (req, res) => {
  res.end('ok');
});
server.on('listening', () => {
  console.log('listening on', server.address().port);
  server.close();
});
server.on('error', (err) => console.error('server error:', err.message));

server.listen(0);

Discussion

  • Be the first to comment on this lesson.