> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 04. Events & EventEmitter

> Master the Event-Driven Architecture of Node.js using the EventEmitter class.

<img src="https://mintcdn.com/devweeekends/X0Fp4X8lMl-ZftoO/images/courses/node-crash-course/event-loop.svg?fit=max&auto=format&n=X0Fp4X8lMl-ZftoO&q=85&s=37c8f6eab40e1339a7628340b408ff5e" alt="Node.js Event Loop" width="1080" height="1080" data-path="images/courses/node-crash-course/event-loop.svg" />

# Events & EventEmitter

Node.js is built around an **event-driven architecture**. This means that certain objects (called "emitters") emit named events that cause Function objects ("listeners") to be called.

**The Fire Alarm Analogy:** Think of EventEmitter like a building's fire alarm system. The smoke detector (emitter) does not know which people (listeners) are in the building or what they will do when the alarm sounds. Some will grab their laptops, some will call 911, some will head for the exits. The detector's only job is to broadcast the event. This decoupling--the emitter does not need to know about the listeners--is what makes event-driven architecture so powerful and flexible.

For example, a `net.Server` object emits an event each time a peer connects to it; an `fs.ReadStream` emits an event when the file is opened; a stream emits an event whenever data is available to be read.

All objects that emit events are instances of the `EventEmitter` class.

## The `events` Module

To use events, we need the `events` module.

```javascript theme={null}
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
```

## Basic Usage

### Registering a Listener

Use `.on()` to register a listener function for a specific event.

```javascript theme={null}
myEmitter.on('event', () => {
  console.log('An event occurred!');
});
```

### Emitting an Event

Use `.emit()` to trigger the event.

```javascript theme={null}
myEmitter.emit('event');
// Output: An event occurred!
```

## Passing Arguments

You can pass arguments to the event listener.

```javascript theme={null}
myEmitter.on('greet', (name) => {
  console.log(`Hello, ${name}!`);
});

myEmitter.emit('greet', 'Alice');
// Output: Hello, Alice!
```

## Extending EventEmitter

In real-world applications, you usually extend the `EventEmitter` class to create your own modules that emit events.

Let's create a `Logger` class that emits a 'message' event whenever a message is logged.

**logger.js**

```javascript theme={null}
const EventEmitter = require('events');
const uuid = require('uuid'); // Hypothetical dependency for IDs

class Logger extends EventEmitter {
  log(msg) {
    // Call event
    this.emit('message', { id: Date.now(), msg });
    console.log(msg);
  }
}

module.exports = Logger;
```

**app.js**

```javascript theme={null}
const Logger = require('./logger');
const logger = new Logger();

// Register listener
logger.on('message', (data) => {
  console.log('Called Listener:', data);
});

logger.log('Hello World');
logger.log('Hi there');
```

## Handling Errors

When an error occurs within an `EventEmitter` instance, the typical action is for an 'error' event to be emitted. If an `EventEmitter` does not have at least one listener registered for the 'error' event, and an 'error' event is emitted, the error is thrown, a stack trace is printed, and the Node.js process exits.

This is one of the most important patterns in Node.js: **an unhandled 'error' event will crash your entire process.** In production, this means your server goes down for all users because of a single unhandled error in one EventEmitter instance. Always register an 'error' listener.

```javascript theme={null}
myEmitter.on('error', (err) => {
  console.error('Whoops! There was an error');
});

myEmitter.emit('error', new Error('Something went wrong!'));
```

## Advanced Event Patterns

### One-Time Listeners

Use `.once()` for listeners that should only fire once.

```javascript theme={null}
myEmitter.once('connect', () => {
  console.log('Connected! This will only log once.');
});

myEmitter.emit('connect'); // Logs message
myEmitter.emit('connect'); // Nothing happens
```

### Removing Listeners

```javascript theme={null}
const greet = () => console.log('Hello!');

myEmitter.on('greet', greet);
myEmitter.emit('greet'); // Hello!

myEmitter.removeListener('greet', greet);
// OR: myEmitter.off('greet', greet);
myEmitter.emit('greet'); // Nothing happens

// Remove all listeners for an event
myEmitter.removeAllListeners('greet');

// Remove all listeners for all events
myEmitter.removeAllListeners();
```

### Listener Count and Names

```javascript theme={null}
myEmitter.on('data', () => {});
myEmitter.on('data', () => {});
myEmitter.on('error', () => {});

console.log(myEmitter.listenerCount('data')); // 2
console.log(myEmitter.eventNames()); // ['data', 'error']
```

### Prepending Listeners

Listeners are normally added to the end of the queue. Use `.prependListener()` to add to the beginning.

```javascript theme={null}
myEmitter.on('event', () => console.log('First added'));
myEmitter.prependListener('event', () => console.log('Added first, runs first'));
myEmitter.emit('event');
// Output:
// Added first, runs first
// First added
```

## Building a Custom Event-Driven System

Let's build a practical example: a task queue system.

```javascript theme={null}
const EventEmitter = require('events');

class TaskQueue extends EventEmitter {
  constructor(concurrency = 1) {
    super();
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }

  push(task) {
    this.queue.push(task);
    this.emit('taskAdded', { queueLength: this.queue.length });
    this.process();
  }

  async process() {
    while (this.running < this.concurrency && this.queue.length > 0) {
      const task = this.queue.shift();
      this.running++;
      
      this.emit('taskStarted', { task: task.name });
      
      try {
        const result = await task.execute();
        this.emit('taskCompleted', { task: task.name, result });
      } catch (error) {
        this.emit('taskFailed', { task: task.name, error });
      } finally {
        this.running--;
        this.process(); // Process next task
      }
    }

    if (this.running === 0 && this.queue.length === 0) {
      this.emit('idle');
    }
  }
}

// Usage
const queue = new TaskQueue(2); // 2 concurrent tasks

queue.on('taskAdded', (data) => console.log('Task added. Queue length:', data.queueLength));
queue.on('taskStarted', (data) => console.log('Started:', data.task));
queue.on('taskCompleted', (data) => console.log('Completed:', data.task));
queue.on('taskFailed', (data) => console.error('Failed:', data.task, data.error));
queue.on('idle', () => console.log('All tasks completed!'));

queue.push({
  name: 'Task 1',
  execute: () => new Promise(resolve => setTimeout(() => resolve('Done'), 1000))
});

queue.push({
  name: 'Task 2', 
  execute: () => Promise.resolve('Quick task')
});
```

## Async Iterator Support

Node.js 12+ supports async iterators with events:

```javascript theme={null}
const { on } = require('events');

async function processEvents(emitter) {
  for await (const [data] of on(emitter, 'data')) {
    console.log('Received:', data);
    if (data === 'close') break;
  }
  console.log('Done processing events');
}

// Usage
const emitter = new EventEmitter();
processEvents(emitter);

emitter.emit('data', 'first');
emitter.emit('data', 'second');
emitter.emit('data', 'close');
```

## EventEmitter vs Other Patterns

When should you use EventEmitter versus callbacks, promises, or streams? This decision comes up often in Node.js design:

| Pattern                   | Communication                      | Coupling                                 | Best for                                           |
| ------------------------- | ---------------------------------- | ---------------------------------------- | -------------------------------------------------- |
| **Callback**              | One caller, one handler            | Tight -- caller must know the handler    | Single async operation with one result             |
| **Promise / async-await** | One caller, one result             | Moderate -- chain of .then() or await    | Sequential async operations                        |
| **EventEmitter**          | One emitter, many listeners        | Loose -- emitter does not know listeners | Multiple consumers, ongoing events, plugin systems |
| **Stream**                | One producer, one consumer (piped) | Moderate -- connected via pipe           | Ordered data flow with backpressure                |

**Decision framework:**

* If a thing happens **once** and you need the result: use a Promise.
* If a thing happens **multiple times** and you need to notify **one consumer**: use a Stream.
* If a thing happens **multiple times** and you need to notify **many consumers** who do different things: use EventEmitter.
* If you are building a **plugin system** where external code should react to internal events without modifying the core: EventEmitter is the natural fit.

**Edge case -- mixing EventEmitter with Promises:** A common mistake is emitting an event inside a Promise chain and expecting the listener to run synchronously within that chain. Listeners registered with `.on()` are synchronous -- they run immediately and in order when `.emit()` is called. But if a listener throws, it throws synchronously in the `.emit()` call, which can break your Promise chain in unexpected ways. Wrap `.emit()` in a try/catch if your listeners are not fully under your control.

## Best Practices

<Tip>
  1. **Always handle errors**: An unhandled 'error' event crashes the process
  2. **Remove listeners when done**: Prevent memory leaks in long-running apps
  3. **Use `once()` for one-time events**: Connection, initialization
  4. **Set max listeners when needed**: `emitter.setMaxListeners(20)`
  5. **Use named functions**: Easier to remove than anonymous functions
</Tip>

<Warning>
  **Memory leak pitfall:** If you register listeners inside a request handler or a loop without removing them, you will leak memory. Node.js warns you when an emitter exceeds 10 listeners (the default max) by printing `MaxListenersExceededWarning`. Do not silence this warning by blindly calling `setMaxListeners(Infinity)`. Instead, investigate why listeners are accumulating--it almost always indicates a bug where listeners are being added repeatedly without being cleaned up. In long-running servers, this is one of the most common causes of gradual memory growth that eventually crashes the process.
</Warning>

## Summary

* **EventEmitter** is the foundation of Node.js async patterns--streams, HTTP servers, and most core APIs are built on it
* Use `.on()` to register listeners, `.emit()` to trigger events
* Always handle **'error'** events to prevent crashes--this is non-negotiable in production
* Use `.once()` for one-time events like connection establishment or initialization
* Extend EventEmitter to create your own event-driven classes
* Clean up listeners with `.off()` or `.removeListener()` to prevent memory leaks in long-running processes
* Watch for `MaxListenersExceededWarning` as an early signal of memory leaks

### EventEmitter Performance Characteristics

| Operation                   | Performance | Notes                                                                   |
| --------------------------- | ----------- | ----------------------------------------------------------------------- |
| `emit()` with 0 listeners   | \~50ns      | Nearly free -- no work to do                                            |
| `emit()` with 1 listener    | \~100ns     | Direct function call                                                    |
| `emit()` with 10 listeners  | \~500ns     | Linear in listener count                                                |
| `emit()` with 100 listeners | \~5us       | Still fast, but consider if this is a design smell                      |
| `on()` / `addListener()`    | O(1)        | Pushes to internal array                                                |
| `removeListener()`          | O(n)        | Searches array by reference; use named functions to make this practical |

**Edge case -- listener ordering guarantees:** Listeners fire in the order they were registered. This is a guarantee, not an implementation detail. Some codebases rely on this ordering for middleware-like patterns. However, if you use `prependListener()`, that listener jumps to the front. Mixing `on()` and `prependListener()` on the same event across multiple modules can create execution orders that are difficult to reason about. If ordering matters, document it explicitly or use a single orchestrating listener.
