The EventEmitter class is the foundation of Node.js event-driven architecture, enabling communication between objects through events.
Basic Usage#
1import { EventEmitter } from 'events';
2
3// Create an emitter
4const emitter = new EventEmitter();
5
6// Listen for event
7emitter.on('greet', (name) => {
8 console.log(`Hello, ${name}!`);
9});
10
11// Emit event
12emitter.emit('greet', 'Alice');
13// Output: Hello, Alice!
14
15// Multiple listeners
16emitter.on('greet', (name) => {
17 console.log(`Welcome, ${name}!`);
18});
19
20emitter.emit('greet', 'Bob');
21// Output: Hello, Bob!
22// Output: Welcome, Bob!Event Methods#
1const emitter = new EventEmitter();
2
3// on() - add listener
4emitter.on('data', (data) => console.log(data));
5
6// once() - listen only once
7emitter.once('connect', () => {
8 console.log('Connected!');
9});
10
11emitter.emit('connect'); // Logs: Connected!
12emitter.emit('connect'); // Nothing happens
13
14// addListener() - alias for on()
15emitter.addListener('data', handler);
16
17// prependListener() - add to beginning
18emitter.prependListener('data', firstHandler);
19
20// prependOnceListener()
21emitter.prependOnceListener('init', initHandler);Removing Listeners#
1const emitter = new EventEmitter();
2
3function handler(data) {
4 console.log(data);
5}
6
7emitter.on('data', handler);
8
9// Remove specific listener
10emitter.off('data', handler);
11// or
12emitter.removeListener('data', handler);
13
14// Remove all listeners for event
15emitter.removeAllListeners('data');
16
17// Remove all listeners
18emitter.removeAllListeners();
19
20// Get listener count
21emitter.listenerCount('data');
22
23// Get listeners
24emitter.listeners('data'); // Array of functionsError Handling#
1const emitter = new EventEmitter();
2
3// Always add error listener
4emitter.on('error', (err) => {
5 console.error('Error occurred:', err.message);
6});
7
8// Emitting errors
9emitter.emit('error', new Error('Something went wrong'));
10
11// Without error listener, error events throw
12const unsafeEmitter = new EventEmitter();
13unsafeEmitter.emit('error', new Error('Crash!')); // Throws!
14
15// Error handling pattern
16class SafeEmitter extends EventEmitter {
17 emit(event, ...args) {
18 if (event === 'error' && !this.listenerCount('error')) {
19 console.error('Unhandled error:', args[0]);
20 return false;
21 }
22 return super.emit(event, ...args);
23 }
24}Custom Event Emitter Class#
1import { EventEmitter } from 'events';
2
3class DataStream extends EventEmitter {
4 constructor() {
5 super();
6 this.data = [];
7 }
8
9 push(item) {
10 this.data.push(item);
11 this.emit('data', item);
12
13 if (this.data.length >= 10) {
14 this.emit('threshold', this.data.length);
15 }
16 }
17
18 clear() {
19 this.data = [];
20 this.emit('clear');
21 }
22
23 async fetch(url) {
24 try {
25 this.emit('fetching', url);
26 const response = await fetch(url);
27 const data = await response.json();
28 this.emit('fetched', data);
29 return data;
30 } catch (error) {
31 this.emit('error', error);
32 throw error;
33 }
34 }
35}
36
37// Usage
38const stream = new DataStream();
39
40stream.on('data', item => console.log('New item:', item));
41stream.on('threshold', count => console.log(`Reached ${count} items`));
42stream.on('error', err => console.error('Error:', err));
43
44stream.push({ id: 1, value: 'test' });Async Events#
1import { EventEmitter, once } from 'events';
2
3const emitter = new EventEmitter();
4
5// Wait for single event with promise
6async function waitForData() {
7 const [data] = await once(emitter, 'data');
8 console.log('Received:', data);
9 return data;
10}
11
12// Usage
13setTimeout(() => {
14 emitter.emit('data', { message: 'Hello' });
15}, 1000);
16
17await waitForData();
18
19// Async iterator for events (Node.js 13+)
20import { on } from 'events';
21
22async function processEvents() {
23 const asyncIterator = on(emitter, 'message');
24
25 for await (const [message] of asyncIterator) {
26 console.log('Message:', message);
27
28 if (message === 'stop') {
29 break;
30 }
31 }
32}
33
34// With timeout
35import { setTimeout as delay } from 'timers/promises';
36
37async function waitWithTimeout() {
38 const ac = new AbortController();
39
40 setTimeout(() => ac.abort(), 5000);
41
42 try {
43 const [data] = await once(emitter, 'data', {
44 signal: ac.signal
45 });
46 return data;
47 } catch (err) {
48 if (err.name === 'AbortError') {
49 throw new Error('Timeout waiting for data');
50 }
51 throw err;
52 }
53}Event Patterns#
1// Pub/Sub pattern
2class EventBus extends EventEmitter {
3 subscribe(event, callback) {
4 this.on(event, callback);
5 return () => this.off(event, callback); // Unsubscribe function
6 }
7
8 publish(event, data) {
9 this.emit(event, data);
10 }
11}
12
13const bus = new EventBus();
14
15const unsubscribe = bus.subscribe('user:login', (user) => {
16 console.log('User logged in:', user.name);
17});
18
19bus.publish('user:login', { name: 'Alice' });
20unsubscribe(); // Stop listening
21
22// Wildcard events (custom implementation)
23class WildcardEmitter extends EventEmitter {
24 emit(event, ...args) {
25 super.emit(event, ...args);
26
27 // Emit to wildcard listeners
28 const parts = event.split(':');
29 while (parts.length > 0) {
30 parts.pop();
31 const wildcard = parts.join(':') + ':*';
32 super.emit(wildcard, event, ...args);
33 }
34 super.emit('*', event, ...args);
35 }
36}
37
38const wild = new WildcardEmitter();
39wild.on('user:*', (event, data) => {
40 console.log(`User event: ${event}`, data);
41});
42wild.emit('user:login', { id: 1 });
43wild.emit('user:logout', { id: 1 });Configuration#
1import { EventEmitter } from 'events';
2
3const emitter = new EventEmitter();
4
5// Set max listeners (default is 10)
6emitter.setMaxListeners(20);
7
8// Get max listeners
9emitter.getMaxListeners();
10
11// Global default
12EventEmitter.defaultMaxListeners = 15;
13
14// Get event names
15emitter.on('data', () => {});
16emitter.on('error', () => {});
17emitter.eventNames(); // ['data', 'error']
18
19// Warning for too many listeners
20// Node.js warns when > maxListeners
21emitter.on('newListener', (event, listener) => {
22 console.log(`Adding listener for ${event}`);
23});
24
25emitter.on('removeListener', (event, listener) => {
26 console.log(`Removed listener for ${event}`);
27});Real-World Examples#
1// HTTP server events
2import http from 'http';
3
4const server = http.createServer();
5
6server.on('request', (req, res) => {
7 res.end('Hello World');
8});
9
10server.on('listening', () => {
11 console.log('Server is listening');
12});
13
14server.on('error', (err) => {
15 console.error('Server error:', err);
16});
17
18server.on('close', () => {
19 console.log('Server closed');
20});
21
22server.listen(3000);
23
24// Stream events
25import fs from 'fs';
26
27const readStream = fs.createReadStream('file.txt');
28
29readStream.on('data', (chunk) => {
30 console.log('Received chunk:', chunk.length);
31});
32
33readStream.on('end', () => {
34 console.log('Finished reading');
35});
36
37readStream.on('error', (err) => {
38 console.error('Read error:', err);
39});
40
41// Database connection events
42class Database extends EventEmitter {
43 constructor(config) {
44 super();
45 this.config = config;
46 this.connected = false;
47 }
48
49 async connect() {
50 try {
51 this.emit('connecting');
52 // Simulate connection
53 await new Promise(r => setTimeout(r, 100));
54 this.connected = true;
55 this.emit('connected');
56 } catch (error) {
57 this.emit('error', error);
58 }
59 }
60
61 async query(sql) {
62 if (!this.connected) {
63 throw new Error('Not connected');
64 }
65 this.emit('query', sql);
66 // Execute query...
67 this.emit('result', { rows: [] });
68 }
69
70 disconnect() {
71 this.connected = false;
72 this.emit('disconnected');
73 }
74}
75
76const db = new Database({ host: 'localhost' });
77db.on('connecting', () => console.log('Connecting...'));
78db.on('connected', () => console.log('Connected!'));
79db.on('query', sql => console.log('Executing:', sql));Memory Management#
1import { EventEmitter } from 'events';
2
3// Avoid memory leaks
4class Component extends EventEmitter {
5 constructor(eventBus) {
6 super();
7 this.eventBus = eventBus;
8 this.handlers = [];
9 }
10
11 init() {
12 // Store reference to handler
13 const handler = (data) => this.handleData(data);
14 this.handlers.push({ event: 'data', handler });
15 this.eventBus.on('data', handler);
16 }
17
18 handleData(data) {
19 console.log('Data:', data);
20 }
21
22 destroy() {
23 // Clean up all handlers
24 for (const { event, handler } of this.handlers) {
25 this.eventBus.off(event, handler);
26 }
27 this.handlers = [];
28 this.removeAllListeners();
29 }
30}
31
32// Using AbortController for cleanup
33class ManagedEmitter extends EventEmitter {
34 listen(event, handler, signal) {
35 this.on(event, handler);
36
37 if (signal) {
38 signal.addEventListener('abort', () => {
39 this.off(event, handler);
40 });
41 }
42
43 return () => this.off(event, handler);
44 }
45}
46
47// Usage with AbortController
48const controller = new AbortController();
49const emitter = new ManagedEmitter();
50
51emitter.listen('data', handleData, controller.signal);
52
53// Later: cleanup all listeners
54controller.abort();TypeScript Support#
1import { EventEmitter } from 'events';
2
3// Typed events
4interface MyEvents {
5 data: (payload: { id: number; value: string }) => void;
6 error: (error: Error) => void;
7 complete: () => void;
8}
9
10class TypedEmitter extends EventEmitter {
11 on<K extends keyof MyEvents>(
12 event: K,
13 listener: MyEvents[K]
14 ): this {
15 return super.on(event, listener);
16 }
17
18 emit<K extends keyof MyEvents>(
19 event: K,
20 ...args: Parameters<MyEvents[K]>
21 ): boolean {
22 return super.emit(event, ...args);
23 }
24}
25
26const emitter = new TypedEmitter();
27
28emitter.on('data', (payload) => {
29 console.log(payload.id); // Type-safe
30});
31
32emitter.emit('data', { id: 1, value: 'test' }); // Type-checkedBest Practices#
Event Design:
✓ Use descriptive event names
✓ Namespace events (user:login)
✓ Document event payloads
✓ Always handle 'error' events
Memory Management:
✓ Remove listeners when done
✓ Use once() for one-time events
✓ Set appropriate maxListeners
✓ Clean up in destroy/cleanup methods
Error Handling:
✓ Always add error listener
✓ Use try/catch in async handlers
✓ Emit errors, don't throw
✓ Log unhandled errors
Performance:
✓ Avoid too many listeners
✓ Use removeAllListeners sparingly
✓ Consider event pooling for high volume
✓ Profile listener execution time
Avoid:
✗ Anonymous functions (can't remove)
✗ Memory leaks from listeners
✗ Throwing in event handlers
✗ Ignoring error events
Conclusion#
The EventEmitter is central to Node.js async architecture. Use it to build decoupled, event-driven systems. Always handle error events, clean up listeners to prevent memory leaks, and consider using once() for single-use events. The async utilities (once, on) enable promise-based event handling. For TypeScript, create typed emitter classes for better type safety.