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, SecondExtending 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 errorAsync 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 listenerReal-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.