Skip to content
Laviyx Learn

Try

Node.js and Express course menu

Node.js Events and EventEmitter Explained

Learn the event-driven style at the heart of Node.js: creating an EventEmitter, emitting and listening for events with on and once, passing data, removing listeners, the special error event, extending EventEmitter in your own classes, and awaiting events with once.

Beginner8 min readUpdated
On this page

Much of Node.js is event-driven: a server emits a request event for each incoming request, a stream emits data and end, a process emits exit. All of these are built on the EventEmitter class from node:events, which you can use in your own code too.

Emit and listen#

JavaScript
import { EventEmitter } from "node:events";

const shop = new EventEmitter();

shop.on("order", (order) => {
  console.log(`New order #${order.id}: ${order.items} items, ₹${order.total}`);
});
shop.on("order", (order) => {
  if (order.total > 2000) console.log(`Order #${order.id} qualifies for free delivery`);
});

shop.emit("order", { id: 101, items: 2, total: 850 });
shop.emit("order", { id: 102, items: 5, total: 3400 });
console.log("Listeners for 'order':", shop.listenerCount("order"));
Output
New order #101: 2 items, ₹850
New order #102: 5 items, ₹3400
Order #102 qualifies for free delivery
Listeners for 'order': 2
  • on(name, listener) registers a function to run each time the event is emitted.
  • emit(name, ...args) calls every listener for that event, synchronously, in the order they were added, passing the arguments.
  • An event can have many listeners, which keeps the code that produces events separate from the code that reacts to them.

once and removing listeners#

JavaScript
import { EventEmitter } from "node:events";

const app = new EventEmitter();

app.once("ready", () => console.log("Ready! (runs only the first time)"));
app.emit("ready");
app.emit("ready");

function logLogin(user) {
  console.log(`${user} logged in`);
}
app.on("login", logLogin);
app.emit("login", "asha");
app.off("login", logLogin);              // removeListener
app.emit("login", "ravi");               // no listeners now
console.log("Done");
Output
Ready! (runs only the first time)
asha logged in
Done

To remove a listener you need a reference to the same function, so use named functions for listeners you plan to remove.

The error event#

The error event is special: if something emits error and nobody is listening, Node throws the error, which can crash the program. Always listen for error on emitters that can fail (streams, sockets, servers):

JavaScript
import { EventEmitter } from "node:events";

const payments = new EventEmitter();
payments.on("error", (err) => console.log("Payment failed:", err.message));

payments.emit("error", new Error("Card declined"));
console.log("The program keeps running");
Output
Payment failed: Card declined
The program keeps running

Your own event-emitting class#

Extend EventEmitter to give a class its own events:

JavaScript
import { EventEmitter } from "node:events";

class Thermostat extends EventEmitter {
  #temperature = 22;

  set(value) {
    const previous = this.#temperature;
    this.#temperature = value;
    this.emit("change", { previous, current: value });
    if (value > 30) this.emit("too-hot", value);
  }
}

const living = new Thermostat();
living.on("change", ({ previous, current }) => console.log(`Temperature ${previous} → ${current} °C`));
living.on("too-hot", (t) => console.log(`Warning: ${t} °C, turning on the fan`));

living.set(25);
living.set(32);
Output
Temperature 22 → 25 °C
Temperature 25 → 32 °C
Warning: 32 °C, turning on the fan

Awaiting an event#

once from node:events returns a promise that resolves the next time an event fires, which fits nicely with async/await:

JavaScript
import { EventEmitter, once } from "node:events";

const download = new EventEmitter();
setTimeout(() => download.emit("done", "report.pdf", 2048), 50);

console.log("Waiting for the download...");
const [file, size] = await once(download, "done");
console.log(`Downloaded ${file} (${size} bytes)`);
Output
Waiting for the download...
Downloaded report.pdf (2048 bytes)

Events or callbacks/promises?#

Use promises (async/await) for one-off results: "read this file", "query the database". Use events when something can happen many times or when several parts of a program react to the same thing: incoming requests, chat messages, progress updates, state changes.

Common mistakes#

Practice#

Practice exercise

Create a Timer class extending EventEmitter that, when started with a number of ticks, emits tick (with the tick number) every 100 ms and then finished. Log each tick and "Finished!".

Show hint

In start(count), use setInterval, emit tick with an increasing counter, and when it reaches count, clearInterval and emit finished.

Show solution
JavaScript
import { EventEmitter } from "node:events";

class Timer extends EventEmitter {
  start(count) {
    let n = 0;
    const id = setInterval(() => {
      n += 1;
      this.emit("tick", n);
      if (n === count) {
        clearInterval(id);
        this.emit("finished");
      }
    }, 100);
  }
}

const timer = new Timer();
timer.on("tick", (n) => console.log(`Tick ${n}`));
timer.on("finished", () => console.log("Finished!"));
timer.start(3);
Output
Tick 1
Tick 2
Tick 3
Finished!

Key takeaway#

Next: Streams.

Finished this lesson?