Event Emitters
Using and creating event-driven architectures with EventEmitter
Challenge
Section titled “Challenge”Your application has multiple components that need to communicate with each other without tight coupling. How do you implement a publish-subscribe pattern?
Explanation
Section titled “Explanation”EventEmitter is Node.js’s implementation of the observer pattern. It allows objects to emit named events and other objects to listen for those events, enabling decoupled communication.
EventEmitter Basics
Section titled “EventEmitter Basics”const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const emitter = new MyEmitter();
// Listen for an eventemitter.on('start', (data) => { console.log('Started with:', data);});
// Emit an eventemitter.emit('start', { time: Date.now() });
// Remove listenerfunction handleError(err) { console.error('Error:', err);}
emitter.on('error', handleError);emitter.off('error', handleError);
// Listen onceemitter.once('connection', () => { console.log('First connection only');});Solution: Custom Event-Driven Server
Section titled “Solution: Custom Event-Driven Server”const EventEmitter = require('events');const http = require('http');
class CustomServer extends EventEmitter { constructor(port) { super(); this.port = port; this.requestCount = 0; }
start() { const server = http.createServer((req, res) => { this.requestCount++;
// Emit request event this.emit('request', { method: req.method, url: req.url, count: this.requestCount });
res.writeHead(200); res.end('OK'); });
server.listen(this.port, () => { this.emit('started', { port: this.port }); }); }}
// Usageconst server = new CustomServer(3000);
server.on('started', (data) => { console.log(`Server started on port ${data.port}`);});
server.on('request', (data) => { console.log(`Request #${data.count}: ${data.method} ${data.url}`);
if (data.count === 100) { server.emit('milestone', { count: 100 }); }});
server.on('milestone', (data) => { console.log(`Milestone reached: ${data.count} requests`);});
server.start();Advanced Event Emitter Patterns
Section titled “Advanced Event Emitter Patterns”const EventEmitter = require('events');
// Async event handlingclass AsyncEmitter extends EventEmitter { async emitAsync(event, ...args) { const listeners = this.listeners(event);
for (const listener of listeners) { await listener(...args); } }}
// Max listeners (prevent memory leaks)const emitter = new EventEmitter();emitter.setMaxListeners(10);
emitter.on('warning', () => { if (emitter.listenerCount('event') > 5) { console.warn('Too many listeners'); }});
// Prefix all eventsclass PrefixedEmitter extends EventEmitter { emit(event, ...args) { return super.emit(`prefix:${event}`, ...args); }
on(event, listener) { return super.on(`prefix:${event}`, listener); }}
// Chaining emittersclass PipedEmitter extends EventEmitter { pipe(target) { this.on('data', (data) => { target.emit('data', data); }); return target; }}Practical Example: State Machine
Section titled “Practical Example: State Machine”const EventEmitter = require('events');
class StateMachine extends EventEmitter { constructor() { super(); this.state = 'idle'; }
setState(newState) { const oldState = this.state; this.state = newState;
this.emit('statechange', { oldState, newState }); this.emit(`enter:${newState}`); this.emit(`leave:${oldState}`); }}
// Usageconst fsm = new StateMachine();
fsm.on('enter:running', () => { console.log('Entered running state');});
fsm.on('leave:idle', () => { console.log('Left idle state');});
fsm.on('statechange', ({ oldState, newState }) => { console.log(`State changed: ${oldState} -> ${newState}`);});
fsm.setState('running');fsm.setState('idle');Error Handling
Section titled “Error Handling”const EventEmitter = require('events');
const emitter = new EventEmitter();
// Error event is special - thrown if no listeneremitter.on('error', (err) => { console.error('Handled error:', err.message);});
// emit with erroremitter.emit('error', new Error('Something went wrong'));
// All emitters should handle errorsclass SafeEmitter extends EventEmitter { safeEmit(event, ...args) { try { this.emit(event, ...args); } catch (err) { this.emit('error', err); } }}Memory Leak Prevention
Section titled “Memory Leak Prevention”const EventEmitter = require('events');
// ❌ Potential memory leakconst emitter = new EventEmitter();
function onData(data) { console.log(data);}
// Adding listeners repeatedlyfor (let i = 0; i < 1000; i++) { emitter.on('data', onData); // Multiple copies!}
// ✅ Better: Clean up listenersemitter.removeAllListeners('data');emitter.on('data', onData); // Only one listener
// ✅ Or use onceemitter.once('data', onData);
// ✅ Check listener countconsole.log(emitter.listenerCount('data'));
// ✅ Set max listeners limitemitter.setMaxListeners(10);