Node.js: Events and EventEmitter

Last updated: 2026-08-26

Alice is a backend engineer developing an e-commerce order system. Initially, she had the order creation function directly call three modules—sending emails, updating inventory, and logging—but this meant that every time she added a new feature, she had to modify the core order code, making the system increasingly fragile. Later, she refactored the code using EventEmitter: the order module is now responsible only for triggering the order:created event, while each service listens independently without interfering with one another. Adding “SMS notifications” requires only adding a listener, with zero changes to the core code. This publish-subscribe architecture reduced the system’s maintenance costs by 60%.

1. What You'll Learn



2. Core Concepts of EventEmitter

EventEmitter is the foundational class for Node.js's event-driven architecture, located in the events module. It maintains a mapping from event names to arrays of listener functions. When emit() triggers an event, all registered listeners are executed synchronously in the order they were registered.

100%
flowchart LR
    subgraph Emitter["EventEmitter(Posted by)"]
        E1[emit - order:created]
    end
    subgraph Listeners["Listener(Subscribers)"]
        L1[Listener1:Send an Email]
        L2[Listener2:Update Inventory]
        L3[Listener3:Log Entry]
    end
    E1 --> L1
    E1 --> L2
    E1 --> L3
    style Emitter fill:#e1f5fe
    style Listeners fill:#f3e5f5
JAVASCRIPT
const EventEmitter = require('events');

const emitter = new EventEmitter();

emitter.on('greet', (name) => {
  console.log(`Hello, ${name}!`);
});

emitter.emit('greet', 'Alice');
TEXT 📖 Display only
Hello, Alice!


3. Quick Reference for Common EventEmitter Methods

Method Description Return Value
on(event, listener) Register a listener; can be registered multiple times EventEmitter instance
once(event, listener) Register a one-time listener that is automatically removed upon triggering EventEmitter instance
emit(event, ...args) Trigger an event, pass parameters true Has a listener / false Has no listener
off(event, listener) Remove a specified listener EventEmitter instance
removeListener(event, listener) Same as off(), old API EventEmitter instance
removeAllListeners([event]) Remove listeners for all or specified events EventEmitter instance
prependListener(event, listener) Add a listener to the front of the queue EventEmitter instance
prependOnceListener(event, listener) Add a one-time listener at the top EventEmitter instance
listeners(event) Return the event listener array Function[]
listenerCount(event) Number of listeners number
setMaxListeners(n) Set the maximum number of listeners for a single event EventEmitter instance
getMaxListeners() Get the maximum number of listeners number
eventNames() Return all registered event names `(string
rawListeners(event) Return the original listener tagged with once Function[]

▶ Example: Basic use of on/emit/off

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

function onOrderCreated(order) {
  console.log(`[Listener] Order created: #${order.id}`);
}

emitter.on('order:created', onOrderCreated);

emitter.emit('order:created', { id: 1001, product: 'Laptop', qty: 2 });

emitter.off('order:created', onOrderCreated);

const hasListeners = emitter.emit('order:created', { id: 1002, product: 'Mouse', qty: 5 });
console.log('Has listeners:', hasListeners);
▶ Try it Yourself
TEXT 📖 Display only
[Listener] Order created: #1001
Has listeners: false


4. on vs once vs prependListener Comparison

Characteristics on() once() prependListener()
Number of Triggers Triggers Every Time Triggers Only Once, Then Removes It Automatically Triggers Every Time
Registration Location End of Queue End of Queue Beginning of Queue (Executed First)
Typical Scenarios Continuous Message Monitoring Initialization Performed Only Once High-Priority Processing (e.g., Logging)
Auto-remove No Yes No
Corresponding to the single-use version once() prependOnceListener()

▶ Example: "once" triggers only once

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.once('server:ready', (port) => {
  console.log(`Server initialized on port ${port}`);
});

emitter.emit('server:ready', 3000);
emitter.emit('server:ready', 3001);
console.log('Second emit had no effect');
▶ Try it Yourself
TEXT 📖 Display only
Server initialized on port 3000
Second emit had no effect

▶ Example: prependListener is executed first

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.on('data', () => console.log('Normal listener'));
emitter.prependListener('data', () => console.log('Priority listener'));

emitter.emit('data');
▶ Try it Yourself
TEXT 📖 Display only
Priority listener
Normal listener


5. Event Listener Management

By default, Node.js allows a maximum of 10 listeners per event; exceeding this limit will trigger a memory leak warning. This is not a hard limit, but rather a reminder for developers to check whether they have forgotten to remove any listeners.

▶ Example: listenerCount and Memory Leak Warnings

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

for (let i = 0; i < 12; i++) {
  emitter.on('log', () => console.log(`Listener ${i}`));
}

console.log('Listener count:', emitter.listenerCount('log'));
console.log('Max listeners:', emitter.getMaxListeners());
▶ Try it Yourself
TEXT 📖 Display only
(node:1234) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 12 log listeners added. Use emitter.setMaxListeners() to increase limit
Listener count: 12
Max listeners: 10

▶ Example: Adjusting the upper limit with setMaxListeners

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.setMaxListeners(20);

for (let i = 0; i < 15; i++) {
  emitter.on('task', () => {});
}

console.log('No warning: max set to', emitter.getMaxListeners());
console.log('Listener count:', emitter.listenerCount('task'));
▶ Try it Yourself
TEXT 📖 Display only
No warning: max set to 20
Listener count: 15

▶ Example: removeAllListeners cleanup

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.on('tick', () => console.log('tick A'));
emitter.on('tick', () => console.log('tick B'));
emitter.on('tock', () => console.log('tock A'));

emitter.removeAllListeners('tick');

console.log('tick listeners:', emitter.listenerCount('tick'));
console.log('tock listeners:', emitter.listenerCount('tock'));
▶ Try it Yourself
TEXT 📖 Display only
tick listeners: 0
tock listeners: 1


6. Custom Event Classes

In actual development, it is common to inherit from EventEmitter to create event classes with business semantics, rather than using EventEmitter instances directly.

▶ Example: Creating an order event class by extending EventEmitter

JAVASCRIPT
const EventEmitter = require('events');

class OrderEmitter extends EventEmitter {
  create(order) {
    this.emit('order:created', order);
  }

  cancel(orderId) {
    this.emit('order:cancelled', { orderId, cancelledAt: new Date().toISOString() });
  }

  ship(orderId, trackingNo) {
    this.emit('order:shipped', { orderId, trackingNo });
  }
}

const orderEvents = new OrderEmitter();

orderEvents.on('order:created', (order) => {
  console.log(`[Email] Confirmation for order #${order.id}`);
});

orderEvents.on('order:created', (order) => {
  console.log(`[Inventory] Deduct ${order.qty}x ${order.product}`);
});

orderEvents.on('order:cancelled', ({ orderId }) => {
  console.log(`[Refund] Processing refund for #${orderId}`);
});

orderEvents.create({ id: 2001, product: 'Headphones', qty: 3 });
orderEvents.cancel(2001);
▶ Try it Yourself
TEXT 📖 Display only
[Email] Confirmation for order #2001
[Inventory] Deduct 3x Headphones
[Refund] Processing refund for #2001


7. The error Event and Error Handling

EventEmitter has a special error event: if the error event is triggered but there are no listeners, Node.js will throw an exception and terminate the process. This is the most important safety rule in EventEmitter.

▶ Example: Process crash due to failure to handle an error

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.emit('error', new Error('Database connection failed'));
▶ Try it Yourself
TEXT 📖 Display only
events.js:291
      throw er; // Unhandled 'error' event
      ^
Error: Database connection failed
    at Object.<anonymous> (app.js:4:20)

▶ Example: Handling the error event correctly

JAVASCRIPT
const EventEmitter = require('events');
const emitter = new EventEmitter();

emitter.on('error', (err) => {
  console.error(`[Error Handler] ${err.message}`);
});

emitter.emit('error', new Error('Database connection failed'));
console.log('Process continues running');
▶ Try it Yourself
TEXT 📖 Display only
[Error Handler] Database connection failed
Process continues running


8. Comparison of Event-Driven, Callbacks, and Promises

Feature Event-driven (EventEmitter) Callback Promise / async-await
Communication Mode One-to-many (publish-subscribe) One-to-one One-to-one
Number of Triggers Can be triggered multiple times Triggers only once Resolves only once
Typical Scenarios Message Broadcasting, State Changes Completion of I/O Operations Final Results of Asynchronous Operations
Coupling Low (the publisher does not know the subscriber) High (the caller must know the callback) Medium (chaining or await)
Cancelable Can be removed with off() Non-cancelable Non-cancelable (ignorable)
Built-in Support events Module Global Conventions Built-in Language
Error Handling error Event Error Priority Callback .catch() / try-catch


9. Built-in Node.js Modules That Use EventEmitter

Many of Node.js's built-in modules inherit from EventEmitter; almost all stream, server, and process objects are event emitters.

Module/Object Inherits from EventEmitter Common Events
net.Server Yes connection, close, error
http.Server Yes request, connection, close
stream.Readable Yes data, end, error, close
stream.Writable Yes drain, finish, error, close
net.Socket Yes data, connect, end, error
process Yes exit, uncaughtException, SIGINT
child_process.ChildProcess Yes exit, message, error, close
fs.watch() Back to EventEmitter change, error

▶ Example: http.Server using EventEmitter

JAVASCRIPT
const http = require('http');

const server = http.createServer();

server.on('request', (req, res) => {
  console.log(`[Request] ${req.method} ${req.url}`);
  res.end('OK');
});

server.on('connection', (socket) => {
  console.log(`[Connection] New client from ${socket.remoteAddress}`);
});

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});
▶ Try it Yourself
TEXT 📖 Display only
Server listening on port 3000
[Connection] New client from ::ffff:127.0.0.1
[Request] GET /

▶ Example: Readable Stream using EventEmitter

JAVASCRIPT
const { Readable } = require('stream');

const readable = Readable.from(['Hello', ' ', 'World']);

readable.on('data', (chunk) => {
  console.log(`[Data] Received: "${chunk}"`);
});

readable.on('end', () => {
  console.log('[End] No more data');
});
▶ Try it Yourself
TEXT 📖 Display only
[Data] Received: "Hello"
[Data] Received: " "
[Data] Received: "World"
[End] No more data


10. Comprehensive Example: Event-Driven Task Scheduler

Create a TaskScheduler class that supports task registration, event triggering, and responses from multiple listeners. Simulate the notification chain that occurs after a task is created in a real-world scenario.

JAVASCRIPT
const EventEmitter = require('events');

class TaskScheduler extends EventEmitter {
  constructor() {
    super();
    this.tasks = new Map();
    this.nextId = 1;
  }

  addTask(name, payload) {
    const id = this.nextId++;
    const task = {
      id,
      name,
      payload,
      createdAt: new Date().toISOString(),
      status: 'pending'
    };
    this.tasks.set(id, task);
    this.emit('task:added', task);
    return task;
  }

  startTask(id) {
    const task = this.tasks.get(id);
    if (!task) {
      this.emit('error', new Error(`Task #${id} not found`));
      return;
    }
    task.status = 'running';
    task.startedAt = new Date().toISOString();
    this.emit('task:started', task);
  }

  completeTask(id, result) {
    const task = this.tasks.get(id);
    if (!task) {
      this.emit('error', new Error(`Task #${id} not found`));
      return;
    }
    task.status = 'completed';
    task.result = result;
    task.completedAt = new Date().toISOString();
    this.emit('task:completed', task);
  }

  failTask(id, reason) {
    const task = this.tasks.get(id);
    if (!task) {
      this.emit('error', new Error(`Task #${id} not found`));
      return;
    }
    task.status = 'failed';
    task.reason = reason;
    task.failedAt = new Date().toISOString();
    this.emit('task:failed', task);
  }

  getStats() {
    const stats = { total: this.tasks.size, pending: 0, running: 0, completed: 0, failed: 0 };
    for (const task of this.tasks.values()) {
      stats[task.status]++;
    }
    return stats;
  }
}

const scheduler = new TaskScheduler();

scheduler.on('error', (err) => {
  console.error(`[ERROR] ${err.message}`);
});

scheduler.on('task:added', (task) => {
  console.log(`[Logger] Task #${task.id} "${task.name}" added at ${task.createdAt}`);
});

scheduler.on('task:added', (task) => {
  console.log(`[Notifier] New task available: ${task.name}`);
});

scheduler.on('task:started', (task) => {
  console.log(`[Executor] Task #${task.id} is now running...`);
});

scheduler.on('task:completed', (task) => {
  console.log(`[Reporter] Task #${task.id} completed with result: ${task.result}`);
  console.log(`[Cleaner] Releasing resources for task #${task.id}`);
});

scheduler.on('task:failed', (task) => {
  console.log(`[Alerter] Task #${task.id} failed: ${task.reason}`);
});

console.log('=== Adding Tasks ===');
const t1 = scheduler.addTask('Data Import', { source: 'api.example.com', rows: 5000 });
const t2 = scheduler.addTask('Report Generation', { format: 'PDF', quarter: 'Q4' });

console.log('\n=== Starting Tasks ===');
scheduler.startTask(t1.id);
scheduler.startTask(t2.id);

console.log('\n=== Completing / Failing Tasks ===');
scheduler.completeTask(t1.id, '5000 rows imported successfully');
scheduler.failTask(t2.id, 'PDF renderer service unavailable');

console.log('\n=== Stats ===');
console.log(scheduler.getStats());

console.log('\n=== Invalid Operation ===');
scheduler.startTask(999);
TEXT 📖 Display only
=== Adding Tasks ===
[Logger] Task #1 "Data Import" added at 2025-07-03T10:00:00.000Z
[Notifier] New task available: Data Import
[Logger] Task #2 "Report Generation" added at 2025-07-03T10:00:01.000Z
[Notifier] New task available: Report Generation

=== Starting Tasks ===
[Executor] Task #1 is now running...
[Executor] Task #2 is now running...

=== Completing / Failing Tasks ===
[Reporter] Task #1 completed with result: 5000 rows imported successfully
[Cleaner] Releasing resources for task #1
[Alerter] Task #2 failed: PDF renderer service unavailable

=== Stats ===
{ total: 2, pending: 0, running: 0, completed: 1, failed: 1 }

=== Invalid Operation ===
[ERROR] Task #999 not found

❓ FAQ

Q What is the difference between EventEmitter and browser events?
A Node.js's EventEmitter executes synchronously, with listeners running in the order they were registered; browser DOM events are asynchronous, go through capture and bubbling phases, and have a different event object structure than Node.js.
Q What happens if you forget to listen for the error event?
A If no error listeners are registered when emit('error') is called, Node.js will throw the error as an uncaught exception, and the process will exit immediately (unless process.on('uncaughtException') is set).
Q What is the maximum number of listeners an event can have?
A The default limit is 10. Exceeding this limit will trigger a memory leak warning (but will not block execution). This can be adjusted via emitter.setMaxListeners(n); setting it to 0 removes the limit.
Q What is the return value of emit()?
A If the event has at least one listener, it returns true; if it has no listeners, it returns false. This can be used to determine whether the event has been handled.
Q How can I ensure that a listener is automatically removed after being triggered only once?
A Use the once() method to register the listener; it will be automatically removed after being triggered once. This is suitable for scenarios such as initialization and one-time signals.
Q What is the difference between off() and removeListener()?
A They function exactly the same. off() is a new alias introduced in Node.js v10 removeListener() that offers more concise syntax; it is recommended to use off() in new code.
Q What does this refer to in a listener?
A When using ES6 arrow functions, this inherits the outer scope; when using regular functions, this refers to the EventEmitter instance, unless it has been bound to another object via bind().

📖 Summary


📝 Exercises

  1. Complete all the code examples in this lesson and make sure each one runs correctly.
  2. Modify the comprehensive example and add your own extensions
  3. Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
  4. Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
  5. Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏