EventEmitter — Event-Driven Architecture
📖 Concept
The EventEmitter class is the foundation of Node.js's event-driven architecture. Many core modules (http.Server, Stream, process) extend EventEmitter, and it's the primary pattern for decoupled, reactive communication between components.
Core API:
| Method | Description |
|---|---|
emitter.on(event, fn) |
Register a listener (alias: addListener) |
emitter.once(event, fn) |
Register a one-time listener |
emitter.emit(event, ...args) |
Trigger an event synchronously |
emitter.off(event, fn) |
Remove a specific listener (alias: removeListener) |
emitter.removeAllListeners(event) |
Remove all listeners for an event |
emitter.listenerCount(event) |
Get number of listeners |
emitter.eventNames() |
List all event names with listeners |
Key behaviors:
- Synchronous execution:
emit()calls listeners synchronously in registration order - Memory leaks: Default max listeners is 10 — adding more produces a warning. Use
setMaxListeners(N)to adjust. - Error event: If an
'error'event is emitted with no listener, it throws and crashes the process. Always listen for'error'. thisbinding: Regular functions getthisbound to the emitter; arrow functions do not.
When to use EventEmitter:
- Decoupling components (publisher doesn't know about subscribers)
- Building plugin systems
- Logging and monitoring
- WebSocket / real-time communication
- Custom stream implementations
🏠 Real-world analogy: EventEmitter is like a radio station. The station broadcasts (emits) on specific frequencies (event names). Any number of radios (listeners) can tune in. The station doesn't know or care how many radios are listening.
💻 Code Example
1// EventEmitter — Complete Guide23const { EventEmitter } = require("events");45// 1. Basic usage6const emitter = new EventEmitter();78emitter.on("greet", (name) => {9 console.log(`Hello, ${name}!`);10});1112emitter.on("greet", (name) => {13 console.log(`Welcome, ${name}!`);14});1516emitter.emit("greet", "Alice");17// Hello, Alice!18// Welcome, Alice!1920// 2. One-time listener21emitter.once("connect", () => {22 console.log("Connected (fires only once)");23});24emitter.emit("connect"); // "Connected (fires only once)"25emitter.emit("connect"); // (nothing happens)2627// 3. Custom EventEmitter class — Application Logger28class AppLogger extends EventEmitter {29 log(level, message, meta = {}) {30 const entry = {31 timestamp: new Date().toISOString(),32 level,33 message,34 ...meta,35 };36 this.emit("log", entry);37 this.emit(level, entry); // Also emit level-specific event38 }3940 info(message, meta) { this.log("info", message, meta); }41 warn(message, meta) { this.log("warn", message, meta); }42 error(message, meta) { this.log("error", message, meta); }43}4445const logger = new AppLogger();4647// Console transport48logger.on("log", (entry) => {49 const color = { info: "\x1b[36m", warn: "\x1b[33m", error: "\x1b[31m" };50 console.log(51 `${color[entry.level] || ""}[${entry.level.toUpperCase()}] ${entry.message}\x1b[0m`52 );53});5455// File transport (only errors)56logger.on("error", (entry) => {57 // In production: append to error log file58 // fs.appendFileSync("errors.log", JSON.stringify(entry) + "\n");59});6061// Alert transport (critical errors)62logger.on("error", (entry) => {63 if (entry.critical) {64 console.log("🚨 ALERT: Critical error detected!");65 // Send to PagerDuty, Slack, etc.66 }67});6869logger.info("Server started", { port: 3000 });70logger.warn("High memory usage", { usage: "85%" });71logger.error("Database connection failed", { critical: true });7273// 4. Order processing system with events74class OrderProcessor extends EventEmitter {75 async processOrder(order) {76 this.emit("order:received", order);7778 try {79 // Validate80 this.emit("order:validating", order);81 await this.validate(order);82 this.emit("order:validated", order);8384 // Payment85 this.emit("order:payment:processing", order);86 const payment = await this.processPayment(order);87 this.emit("order:payment:complete", { order, payment });8889 // Fulfillment90 this.emit("order:fulfilling", order);91 await this.fulfill(order);92 this.emit("order:complete", order);93 } catch (err) {94 this.emit("order:failed", { order, error: err });95 }96 }9798 async validate(order) { /* validation logic */ }99 async processPayment(order) { return { id: "pay_123" }; }100 async fulfill(order) { /* fulfillment logic */ }101}102103const processor = new OrderProcessor();104105// Attach independent, decoupled handlers106processor.on("order:received", (order) => {107 console.log(`📦 Order ${order.id} received`);108});109110processor.on("order:complete", (order) => {111 console.log(`✅ Order ${order.id} completed`);112 // Send confirmation email113 // Update inventory114 // Update analytics115});116117processor.on("order:failed", ({ order, error }) => {118 console.error(`❌ Order ${order.id} failed: ${error.message}`);119 // Refund payment120 // Notify customer121});122123// 5. Error handling — MUST listen for 'error'124const risky = new EventEmitter();125126// ❌ WITHOUT error listener: emitting 'error' crashes the process127// risky.emit("error", new Error("boom")); // THROWS!128129// ✅ WITH error listener: error is handled gracefully130risky.on("error", (err) => {131 console.error("Handled error:", err.message);132});133risky.emit("error", new Error("boom")); // "Handled error: boom"134135// 6. Memory leak detection136const leaky = new EventEmitter();137// leaky.setMaxListeners(20); // Increase if needed138139for (let i = 0; i < 15; i++) {140 leaky.on("data", () => {}); // Warning after 10!141}142// (node:12345) MaxListenersExceededWarning143144// 7. Async events with EventEmitter145const { on } = require("events");146147async function processEvents() {148 const emitter = new EventEmitter();149150 // Start emitting events after a delay151 setTimeout(() => {152 emitter.emit("data", { value: 1 });153 emitter.emit("data", { value: 2 });154 emitter.emit("data", { value: 3 });155 emitter.emit("close");156 }, 100);157158 // Async iteration over events (Node.js 12.16+)159 // for await (const [event] of on(emitter, "data")) {160 // console.log("Async event:", event);161 // }162}
🏋️ Practice Exercise
Exercises:
- Build a custom EventEmitter-based logger with console, file, and alert transports
- Create an order processing pipeline using events — emit events for each stage and attach independent handlers
- Implement a simple pub/sub system using EventEmitter with topic-based subscriptions
- Build a file watcher that emits
change,add, anddeleteevents usingfs.watch() - Create an EventEmitter that tracks listener count and warns when it exceeds a threshold
- Implement a circuit breaker pattern using EventEmitter to track failures and state changes
⚠️ Common Mistakes
Not listening for the 'error' event — if an error is emitted with no listener, it throws an uncaught exception and crashes the process
Adding too many listeners without cleanup — this causes memory leaks; use
removeListener()oronce()for short-lived listenersAssuming
emit()is asynchronous — listeners are called synchronously, which means a slow listener blocks all subsequent listeners and the event loopUsing arrow functions when you need
thisto refer to the emitter — arrow functions don't bindthis, sothisinside the listener won't be the emitterCreating tight coupling through events — events should carry data, not control flow; listeners should be independently replaceable
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for EventEmitter — Event-Driven Architecture. Login to unlock this feature.