Events Module in Node.js: Event-Driven Architecture

Much of the Node.js core API is built around an asynchronous, event-driven architecture. In this model, certain objects called emitters generate named events, and functions called listeners respond to those events. This design makes it easier to handle I/O operations, streams, and asynchronous workflows in a clean and efficient way.

EventEmitteremit and ononce and prependListenererror eventscaptureRejections

~5 دقیقه مطالعه • بروزرسانی ۶ دی ۱۴۰۴

1. What is EventEmitter?


All objects that emit events are instances of the EventEmitter class. This class provides methods like on() to register listeners and emit() to trigger events.


2. Simple Example


const EventEmitter = require('node:events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();

myEmitter.on('event', () => {
  console.log('an event occurred!');
});
myEmitter.emit('event');

3. Passing Arguments and this


emit() can pass arguments to listeners. In regular functions, this refers to the EventEmitter instance. With arrow functions, this does not reference the EventEmitter.


4. Synchronous vs. Asynchronous


Listeners are called synchronously in the order they were registered. To switch to asynchronous execution, use setImmediate() or process.nextTick().


5. One-Time Execution


once() registers a listener that runs only once and is then removed.


6. Error Events


If an EventEmitter emits an 'error' event without a registered listener, Node.js will throw the error and crash. Always register error listeners to prevent this.


myEmitter.on('error', (err) => {
  console.error('Error occurred:', err);
});

7. captureRejections


Async listeners can cause unhandled Promise rejections. The captureRejections option routes these rejections to the 'error' event or Symbol.for('nodejs.rejection').


8. Special EventEmitter Events


  • 'newListener': Emitted before a new listener is added.
  • 'removeListener': Emitted after a listener is removed.

9. Key EventEmitter Methods


  • on(): Register a listener.
  • once(): Register a one-time listener.
  • emit(): Trigger an event.
  • listeners(): Get a list of listeners.
  • eventNames(): Get registered event names.
  • setMaxListeners(): Set the maximum number of listeners.
  • prependListener(): Add a listener at the beginning of the list.

Conclusion


The Events module is central to Node.js’s event-driven architecture. By using EventEmitter and its methods, developers can build robust asynchronous applications that handle streams, connections, and complex workflows efficiently.


1. prependListener()


Adds a listener to the beginning of the listener array for a given event.


server.prependListener('connection', (stream) => {
  console.log('someone connected!');
});

2. prependOnceListener()


Similar to prependListener(), but the listener is invoked only once and then removed.


server.prependOnceListener('connection', (stream) => {
  console.log('Ah, we have our first user!');
});

3. removeListener() and removeAllListeners()


  • removeListener(eventName, listener): Removes a specific listener.
  • removeAllListeners([eventName]): Removes all listeners or all listeners for a specific event.

Note: Removing listeners added by other modules can cause issues and is considered bad practice.


4. setMaxListeners() and defaultMaxListeners


By default, Node.js warns if more than 10 listeners are added to a single event. setMaxListeners(n) changes this limit for a specific instance, while events.defaultMaxListeners changes the default globally.


5. rawListeners()


Returns an array of raw listeners, including wrappers created by methods like once().


const listeners = emitter.rawListeners('log');
listeners[0].listener(); // Executes the original listener without removing it
listeners[0]();          // Executes and removes the listener

6. errorMonitor


events.errorMonitor allows monitoring of error events without consuming them. These listeners run before regular error listeners.


7. events.once()


Creates a Promise that resolves when the specified event is emitted, or rejects if an error occurs.


const { once, EventEmitter } = require('node:events');
const ee = new EventEmitter();

(async () => {
  const [value] = await once(ee, 'myevent');
  console.log(value);
})();

8. Managing Rejections


With captureRejections enabled, rejected Promises in async listeners are automatically routed to the 'error' event or Symbol.for('nodejs.rejection').


Conclusion


Advanced EventEmitter methods in Node.js provide powerful tools for listener management, error handling, and preventing memory leaks. Using these methods correctly enhances the stability and maintainability of event-driven applications.


1. events.listenerCount()


Returns the number of listeners registered for a given event. Deprecated since v3.2.0; use emitter.listenerCount() instead.


const { EventEmitter, listenerCount } = require('node:events');
const myEmitter = new EventEmitter();
myEmitter.on('event', () => {});
myEmitter.on('event', () => {});
console.log(listenerCount(myEmitter, 'event')); // 2

2. events.on()


Creates an AsyncIterator that iterates over emitted events. Each iteration returns an array of arguments passed to the event.


const { on, EventEmitter } = require('node:events');
(async () => {
  const ee = new EventEmitter();
  process.nextTick(() => {
    ee.emit('foo', 'bar');
    ee.emit('foo', 42);
  });
  for await (const event of on(ee, 'foo')) {
    console.log(event); // ['bar'], [42]
  }
})();

3. events.setMaxListeners()


Sets the maximum number of listeners for one or more EventEmitters or EventTargets. Default is 10, but can be changed globally or per instance.


const { setMaxListeners, EventEmitter } = require('node:events');
const emitter = new EventEmitter();
setMaxListeners(5, emitter);

4. events.addAbortListener()


Safely listens for the abort event on an AbortSignal. Returns a disposable object to easily unsubscribe.


const { addAbortListener } = require('node:events');
function example(signal) {
  const disposable = addAbortListener(signal, () => {
    console.log('Aborted!');
  });
  disposable[Symbol.dispose]();
}

5. EventEmitterAsyncResource


Integrates EventEmitter with AsyncResource for manual async tracking. Events emitted by this class run within its async context.


const { EventEmitterAsyncResource } = require('node:events');
const ee = new EventEmitterAsyncResource({ name: 'Q' });
ee.on('foo', () => {
  console.log('running in async context');
});
ee.emit('foo');

6. EventTarget and NodeEventTarget


Node.js implements its own EventTarget API, similar to the web standard but with differences:


  • No hierarchy or event propagation.
  • Listeners can only be registered once per event type.
  • NodeEventTarget emulates a subset of EventEmitter methods but is not a full replacement.

const target = new EventTarget();
target.addEventListener('foo', (event) => {
  console.log('foo event happened!');
});
target.dispatchEvent(new Event('foo'));

Conclusion


The advanced features of the events module in Node.js provide powerful tools for listener management, async event handling, and integration with AbortSignals and async contexts. Understanding these APIs helps developers build robust, event-driven applications that align with both Node.js and web standards.


نوشته و پژوهش شده توسط دکتر شاهین صیامی