Back to Blog
Node.jsEventsEventEmitterAsync

Node.js EventEmitter Guide

Master the Node.js EventEmitter for event-driven programming with custom events and listeners.

B
Bootspring Team
Engineering
March 18, 2020
6 min read

EventEmitter is the foundation of event-driven architecture in Node.js. Here's how to use it.

Basic Usage#

1const EventEmitter = require('events'); 2 3// Create an instance 4const emitter = new EventEmitter(); 5 6// Register a listener 7emitter.on('greet', (name) => { 8 console.log(`Hello, ${name}!`); 9}); 10 11// Emit an event 12emitter.emit('greet', 'Alice'); 13// Output: Hello, Alice! 14 15// Multiple arguments 16emitter.on('user', (name, age, city) => { 17 console.log(`${name} is ${age} years old from ${city}`); 18}); 19 20emitter.emit('user', 'Bob', 30, 'New York');

Event Methods#

1const EventEmitter = require('events'); 2const emitter = new EventEmitter(); 3 4// on / addListener - add listener 5emitter.on('event', () => console.log('on')); 6emitter.addListener('event', () => console.log('addListener')); 7 8// once - listen only once 9emitter.once('init', () => { 10 console.log('Initialization complete'); 11}); 12emitter.emit('init'); // Logs message 13emitter.emit('init'); // Nothing happens 14 15// off / removeListener - remove listener 16function handler() { 17 console.log('Handler called'); 18} 19 20emitter.on('action', handler); 21emitter.off('action', handler); 22emitter.emit('action'); // Nothing happens 23 24// removeAllListeners 25emitter.on('test', () => {}); 26emitter.on('test', () => {}); 27emitter.removeAllListeners('test'); 28 29// Or remove all listeners for all events 30emitter.removeAllListeners(); 31 32// prependListener - add to beginning 33emitter.on('order', () => console.log('Second')); 34emitter.prependListener('order', () => console.log('First')); 35emitter.emit('order'); 36// Output: First, Second

Extending EventEmitter#

1const EventEmitter = require('events'); 2 3// Class-based approach 4class Server extends EventEmitter { 5 constructor(port) { 6 super(); 7 this.port = port; 8 this.connections = []; 9 } 10 11 start() { 12 console.log(`Server starting on port ${this.port}`); 13 this.emit('start', this.port); 14 15 // Simulate incoming connection 16 setTimeout(() => { 17 this.emit('connection', { id: 1, ip: '127.0.0.1' }); 18 }, 1000); 19 } 20 21 stop() { 22 this.emit('stop'); 23 this.connections = []; 24 } 25} 26 27const server = new Server(3000); 28 29server.on('start', (port) => { 30 console.log(`Server started on port ${port}`); 31}); 32 33server.on('connection', (client) => { 34 console.log(`New connection from ${client.ip}`); 35}); 36 37server.on('stop', () => { 38 console.log('Server stopped'); 39}); 40 41server.start();

Error Handling#

1const EventEmitter = require('events'); 2const emitter = new EventEmitter(); 3 4// Always handle error events 5emitter.on('error', (err) => { 6 console.error('Error occurred:', err.message); 7}); 8 9// Without error handler, Node.js throws 10// emitter.emit('error', new Error('Something broke')); 11 12// Safe error emission 13function safeEmit(emitter, event, ...args) { 14 try { 15 emitter.emit(event, ...args); 16 } catch (err) { 17 emitter.emit('error', err); 18 } 19} 20 21// Using error events in class 22class Database extends EventEmitter { 23 async connect(url) { 24 try { 25 // Simulate connection 26 await this.establishConnection(url); 27 this.emit('connected', url); 28 } catch (err) { 29 this.emit('error', err); 30 } 31 } 32 33 async establishConnection(url) { 34 // Connection logic 35 if (!url) { 36 throw new Error('URL is required'); 37 } 38 } 39} 40 41const db = new Database(); 42db.on('connected', (url) => console.log(`Connected to ${url}`)); 43db.on('error', (err) => console.error('DB Error:', err.message)); 44db.connect(); // Emits error

Async Events#

1const EventEmitter = require('events'); 2 3// Events are synchronous by default 4const emitter = new EventEmitter(); 5 6emitter.on('sync', () => { 7 console.log('Listener 1'); 8}); 9 10emitter.on('sync', () => { 11 console.log('Listener 2'); 12}); 13 14console.log('Before emit'); 15emitter.emit('sync'); 16console.log('After emit'); 17// Output: Before emit, Listener 1, Listener 2, After emit 18 19// Make async with setImmediate 20emitter.on('async', () => { 21 setImmediate(() => { 22 console.log('Async listener'); 23 }); 24}); 25 26// Or use async listeners 27emitter.on('data', async (data) => { 28 const result = await processData(data); 29 console.log('Processed:', result); 30}); 31 32// Promisified once 33const { once } = require('events'); 34 35async function waitForEvent() { 36 const [data] = await once(emitter, 'data'); 37 console.log('Received:', data); 38} 39 40// With timeout 41const { setTimeout: delay } = require('timers/promises'); 42 43async function waitWithTimeout() { 44 const ac = new AbortController(); 45 46 setTimeout(() => ac.abort(), 5000); 47 48 try { 49 const [data] = await once(emitter, 'data', { signal: ac.signal }); 50 return data; 51 } catch (err) { 52 if (err.name === 'AbortError') { 53 throw new Error('Timeout waiting for event'); 54 } 55 throw err; 56 } 57}

Event Patterns#

1const EventEmitter = require('events'); 2 3// Pub/Sub pattern 4class PubSub extends EventEmitter { 5 publish(channel, message) { 6 this.emit(channel, message); 7 } 8 9 subscribe(channel, callback) { 10 this.on(channel, callback); 11 return () => this.off(channel, callback); 12 } 13} 14 15const pubsub = new PubSub(); 16 17const unsubscribe = pubsub.subscribe('news', (msg) => { 18 console.log('News:', msg); 19}); 20 21pubsub.publish('news', 'Breaking news!'); 22unsubscribe(); 23 24// Observer pattern 25class Observable extends EventEmitter { 26 constructor(initialValue) { 27 super(); 28 this._value = initialValue; 29 } 30 31 get value() { 32 return this._value; 33 } 34 35 set value(newValue) { 36 const oldValue = this._value; 37 this._value = newValue; 38 this.emit('change', newValue, oldValue); 39 } 40} 41 42const counter = new Observable(0); 43counter.on('change', (newVal, oldVal) => { 44 console.log(`Changed from ${oldVal} to ${newVal}`); 45}); 46 47counter.value = 1; // Changed from 0 to 1 48counter.value = 5; // Changed from 1 to 5 49 50// Event bus for decoupling 51class EventBus extends EventEmitter { 52 static instance = null; 53 54 static getInstance() { 55 if (!EventBus.instance) { 56 EventBus.instance = new EventBus(); 57 } 58 return EventBus.instance; 59 } 60} 61 62const bus = EventBus.getInstance(); 63bus.on('user:login', (user) => console.log(`${user} logged in`)); 64bus.emit('user:login', 'Alice');

Configuration#

1const EventEmitter = require('events'); 2const emitter = new EventEmitter(); 3 4// Get max listeners (default: 10) 5console.log(emitter.getMaxListeners()); // 10 6 7// Set max listeners 8emitter.setMaxListeners(20); 9 10// Or set globally 11EventEmitter.defaultMaxListeners = 15; 12 13// Set to Infinity for unlimited 14emitter.setMaxListeners(Infinity); 15 16// Get listener count 17emitter.on('test', () => {}); 18emitter.on('test', () => {}); 19console.log(emitter.listenerCount('test')); // 2 20 21// Get listeners 22const listeners = emitter.listeners('test'); 23console.log(listeners.length); // 2 24 25// Get raw listeners (includes wrappers) 26const rawListeners = emitter.rawListeners('test'); 27 28// Get event names 29emitter.on('foo', () => {}); 30emitter.on('bar', () => {}); 31console.log(emitter.eventNames()); // ['test', 'foo', 'bar']

Memory Management#

1const EventEmitter = require('events'); 2 3// Warning: possible memory leak 4const emitter = new EventEmitter(); 5 6for (let i = 0; i < 11; i++) { 7 emitter.on('event', () => {}); 8} 9// MaxListenersExceededWarning 10 11// Proper cleanup 12class Resource extends EventEmitter { 13 constructor() { 14 super(); 15 this.handleData = this.handleData.bind(this); 16 } 17 18 start() { 19 process.on('data', this.handleData); 20 this.emit('started'); 21 } 22 23 handleData(data) { 24 this.emit('data', data); 25 } 26 27 stop() { 28 process.off('data', this.handleData); 29 this.removeAllListeners(); 30 this.emit('stopped'); 31 } 32} 33 34// Using AbortController 35const ac = new AbortController(); 36 37emitter.on('event', handler, { signal: ac.signal }); 38 39// Later, cleanup 40ac.abort(); // Removes the listener

Real-World Examples#

1const EventEmitter = require('events'); 2const fs = require('fs'); 3 4// File watcher 5class FileWatcher extends EventEmitter { 6 constructor(filepath) { 7 super(); 8 this.filepath = filepath; 9 this.watcher = null; 10 } 11 12 start() { 13 this.watcher = fs.watch(this.filepath, (event, filename) => { 14 this.emit(event, filename); 15 }); 16 this.emit('watching', this.filepath); 17 } 18 19 stop() { 20 if (this.watcher) { 21 this.watcher.close(); 22 this.watcher = null; 23 this.emit('stopped'); 24 } 25 } 26} 27 28// Task queue 29class TaskQueue extends EventEmitter { 30 constructor(concurrency = 1) { 31 super(); 32 this.concurrency = concurrency; 33 this.running = 0; 34 this.queue = []; 35 } 36 37 add(task) { 38 this.queue.push(task); 39 this.emit('added', task); 40 this.process(); 41 } 42 43 async process() { 44 while (this.running < this.concurrency && this.queue.length > 0) { 45 const task = this.queue.shift(); 46 this.running++; 47 this.emit('start', task); 48 49 try { 50 const result = await task(); 51 this.emit('complete', result); 52 } catch (err) { 53 this.emit('error', err); 54 } finally { 55 this.running--; 56 this.process(); 57 } 58 } 59 60 if (this.running === 0 && this.queue.length === 0) { 61 this.emit('empty'); 62 } 63 } 64}

Best Practices#

Event Design: ✓ Use descriptive event names ✓ Document event signatures ✓ Emit errors as 'error' event ✓ Keep payloads serializable Memory: ✓ Remove listeners when done ✓ Use once() for one-time events ✓ Set appropriate maxListeners ✓ Clean up in stop/destroy Patterns: ✓ Extend EventEmitter for OOP ✓ Use event bus for decoupling ✓ Promisify with once() ✓ Handle errors consistently Avoid: ✗ Too many listeners per event ✗ Forgetting to clean up ✗ Synchronous heavy work in handlers ✗ Circular event emissions

Conclusion#

EventEmitter enables powerful event-driven patterns in Node.js. Extend it for custom classes, use proper error handling, and clean up listeners to prevent memory leaks. The once() method and promisified events work well with async/await patterns.

Share this article

Help spread the word about Bootspring