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:

  1. Synchronous execution: emit() calls listeners synchronously in registration order
  2. Memory leaks: Default max listeners is 10 — adding more produces a warning. Use setMaxListeners(N) to adjust.
  3. Error event: If an 'error' event is emitted with no listener, it throws and crashes the process. Always listen for 'error'.
  4. this binding: Regular functions get this bound 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

codeTap to expand ⛶
1// EventEmitter — Complete Guide
2
3const { EventEmitter } = require("events");
4
5// 1. Basic usage
6const emitter = new EventEmitter();
7
8emitter.on("greet", (name) => {
9 console.log(`Hello, ${name}!`);
10});
11
12emitter.on("greet", (name) => {
13 console.log(`Welcome, ${name}!`);
14});
15
16emitter.emit("greet", "Alice");
17// Hello, Alice!
18// Welcome, Alice!
19
20// 2. One-time listener
21emitter.once("connect", () => {
22 console.log("Connected (fires only once)");
23});
24emitter.emit("connect"); // "Connected (fires only once)"
25emitter.emit("connect"); // (nothing happens)
26
27// 3. Custom EventEmitter class — Application Logger
28class 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 event
38 }
39
40 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}
44
45const logger = new AppLogger();
46
47// Console transport
48logger.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});
54
55// File transport (only errors)
56logger.on("error", (entry) => {
57 // In production: append to error log file
58 // fs.appendFileSync("errors.log", JSON.stringify(entry) + "\n");
59});
60
61// 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});
68
69logger.info("Server started", { port: 3000 });
70logger.warn("High memory usage", { usage: "85%" });
71logger.error("Database connection failed", { critical: true });
72
73// 4. Order processing system with events
74class OrderProcessor extends EventEmitter {
75 async processOrder(order) {
76 this.emit("order:received", order);
77
78 try {
79 // Validate
80 this.emit("order:validating", order);
81 await this.validate(order);
82 this.emit("order:validated", order);
83
84 // Payment
85 this.emit("order:payment:processing", order);
86 const payment = await this.processPayment(order);
87 this.emit("order:payment:complete", { order, payment });
88
89 // Fulfillment
90 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 }
97
98 async validate(order) { /* validation logic */ }
99 async processPayment(order) { return { id: "pay_123" }; }
100 async fulfill(order) { /* fulfillment logic */ }
101}
102
103const processor = new OrderProcessor();
104
105// Attach independent, decoupled handlers
106processor.on("order:received", (order) => {
107 console.log(`📦 Order ${order.id} received`);
108});
109
110processor.on("order:complete", (order) => {
111 console.log(`✅ Order ${order.id} completed`);
112 // Send confirmation email
113 // Update inventory
114 // Update analytics
115});
116
117processor.on("order:failed", ({ order, error }) => {
118 console.error(`❌ Order ${order.id} failed: ${error.message}`);
119 // Refund payment
120 // Notify customer
121});
122
123// 5. Error handling — MUST listen for 'error'
124const risky = new EventEmitter();
125
126// ❌ WITHOUT error listener: emitting 'error' crashes the process
127// risky.emit("error", new Error("boom")); // THROWS!
128
129// ✅ WITH error listener: error is handled gracefully
130risky.on("error", (err) => {
131 console.error("Handled error:", err.message);
132});
133risky.emit("error", new Error("boom")); // "Handled error: boom"
134
135// 6. Memory leak detection
136const leaky = new EventEmitter();
137// leaky.setMaxListeners(20); // Increase if needed
138
139for (let i = 0; i < 15; i++) {
140 leaky.on("data", () => {}); // Warning after 10!
141}
142// (node:12345) MaxListenersExceededWarning
143
144// 7. Async events with EventEmitter
145const { on } = require("events");
146
147async function processEvents() {
148 const emitter = new EventEmitter();
149
150 // Start emitting events after a delay
151 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);
157
158 // 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:

  1. Build a custom EventEmitter-based logger with console, file, and alert transports
  2. Create an order processing pipeline using events — emit events for each stage and attach independent handlers
  3. Implement a simple pub/sub system using EventEmitter with topic-based subscriptions
  4. Build a file watcher that emits change, add, and delete events using fs.watch()
  5. Create an EventEmitter that tracks listener count and warns when it exceeds a threshold
  6. 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() or once() for short-lived listeners

  • Assuming emit() is asynchronous — listeners are called synchronously, which means a slow listener blocks all subsequent listeners and the event loop

  • Using arrow functions when you need this to refer to the emitter — arrow functions don't bind this, so this inside the listener won't be the emitter

  • Creating 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.