Events & EventEmitter

Reviewed & published by Brayan K

Node's event-driven core is built on the EventEmitter class, which lets one part of your program emit named events and other parts subscribe with listener functions that run whenever those events fire.

Part of the free Node.js course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll know how to create an emitter, attach and remove listeners, run a listener only once, build your own class that emits events, and handle the special 'error' event so a stray failure never crashes your program.

What You'll Learn in This Lesson

1️⃣ Creating an Emitter: .on() and .emit()

The pattern has two halves. You subscribe to an event with .on(name, listener), giving it the name of an event and a function to run. Later, you fire that event with .emit(name, ...args), and Node runs every listener subscribed to that name, passing along any extra arguments as data.

Read the worked example below — every line is explained — then run it yourself. Notice how the data you pass to .emit() arrives as the listener's argument.

// EventEmitter is a built-in class. Pull it off the 'node:events' module.
const { EventEmitter } = require('node:events');

// Create an emitter — think of it as a tiny radio station.
const radio = new EventEmitter();

// .on(name, listener) SUBSCRIBES: run this function whenever 'news' fires.
radio.on('news', (headline) => {
  console.log(`Listener heard: ${headline}`);
});

// .emit(name, ...args) FIRES the event and passes data to every listener.
radio.emit('news', 'Node 22 released');   // -> Listener heard: Node 22 released
radio.emit('news', 'Events are easy');     // -> Listener heard: Events are easy

// Listeners run SYNCHRONOUSLY, in the order they were registered.

2️⃣ Many Listeners, .once(), and Removing Them

One event can have many listeners — they all run, in the order they were added. Three methods give you fine control:

Trace the output below: the .once() listener fires on the first emit only, and .off() stops tick after the first call.

const { EventEmitter } = require('node:events');
const bus = new EventEmitter();

// You can attach MANY listeners to the same event — all of them run.
bus.on('start', () => console.log('A: warming up'));
bus.on('start', () => console.log('B: ready'));

// .once() runs ONE time, then removes itself automatically.
bus.once('start', () => console.log('C: this fires only once'));

// A named function so we can remove it later.
function onTick(n) { console.log(`tick ${n}`); }
bus.on('tick', onTick);

bus.emit('start');   // A, B, and C all run
bus.emit('start');   // A and B run again — C is already gone

bus.emit('tick', 1);            // -> tick 1
bus.off('tick', onTick);        // .off() removes the listener (alias: removeListener)
bus.emit('tick', 2);            // nothing — no listener left

3️⃣ Your Own Emitter Class & the 'error' Event

Most real Node objects that emit events — servers, streams, sockets — are classes that extend EventEmitter. You can do the same: class Logger extends EventEmitter gives your class .on(), .emit(), and all the rest, so it can announce what it's doing.

One event name is special: 'error'. If an emitter emits 'error' and no listener is registered for it, Node throws and the process exits. Always attach an .on('error', ...) handler to any emitter that might fail.

const { EventEmitter } = require('node:events');

// Your own classes can BE emitters by extending EventEmitter.
class Logger extends EventEmitter {
  write(message) {
    // Do the work, then announce it by emitting a custom event.
    this.emit('log', { message, time: Date.now() });
  }
}

const logger = new Logger();

// Subscribe to the custom 'log' event this class emits.
logger.on('log', (entry) => {
  console.log(`[LOG] ${entry.message}`);
});

logger.write('user signed in');   // -> [LOG] user signed in

// 'error' is SPECIAL: if you emit it with no listener, the process CRASHES.
logger.on('error', (err) => {
  console.log(`Handled: ${err.message}`);
});
logger.emit('error', new Error('disk full'));  // -> Handled: disk full

Your turn. The program below works once you fill in the three blanks marked ___. Follow the 👉 hints, then run it and compare with the expected output.

const { EventEmitter } = require('node:events');

// 🎯 YOUR TURN — replace each ___ then run it.

// 1) Create a new emitter
const emitter = new ___();          // 👉 the class you imported above

// 2) Register a listener for the 'greet' event
emitter.___('greet', (name) => {    // 👉 the method that SUBSCRIBES to an event
  console.log(`Hello, ${name}!`);
});

// 3) Fire the 'greet' event and pass a name along
emitter.___('greet', 'Sam');        // 👉 the method that FIRES the event

// ✅ Expected output:
//    Hello, Sam!

Mini-Challenge: A Tiny Order Tracker

No blanks this time — just a brief and an outline to keep you on track. Write it yourself, run it, and check your output against the example in the comments. This is exactly the kind of small program that builds real fluency with events.

const { EventEmitter } = require('node:events');

// 🎯 MINI-CHALLENGE: a tiny order tracker
// 1. Create an EventEmitter called 'shop'.
// 2. Register a listener for the 'order' event that logs
//    "New order: <item>" using the data you pass in.
// 3. Use .once() for a 'open' event that logs "Shop is open!".
// 4. Emit 'open' twice, then emit 'order' with 'Coffee'.
//
// ✅ Example output:
//    Shop is open!
//    New order: Coffee

// your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — EventEmitter

ConceptNode Syntax
Create an emitternew EventEmitter()
Listen for an eventemitter.on('e', fn)
Listen one time onlyemitter.once('e', fn)
Fire an eventemitter.emit('e', data)
Remove a listeneremitter.off('e', fn)
Count listenersemitter.listenerCount('e')

🎉 Lesson Complete!

Practice quiz

Which module provides the EventEmitter class?

  • node:emitter
  • node:events
  • node:stream
  • node:bus

Answer: node:events. EventEmitter is pulled from the built-in node:events module.

What does emitter.on('news', fn) do?

  • Fires the 'news' event
  • Removes a listener for 'news'
  • Subscribes fn to run whenever 'news' is emitted
  • Runs fn exactly once

Answer: Subscribes fn to run whenever 'news' is emitted. .on() subscribes a listener that runs every time the named event is emitted.

Which method fires an event and passes data to every listener?

  • .emit(name, ...args)
  • .on(name, fn)
  • .once(name, fn)
  • .off(name, fn)

Answer: .emit(name, ...args). .emit(name, ...args) fires the event and passes the extra arguments to each listener.

How does .once() differ from .on()?

  • It never runs the listener
  • It runs the listener on every emit, twice as fast
  • It only works for the 'error' event
  • It runs the listener one time, then removes it automatically

Answer: It runs the listener one time, then removes it automatically. .once() runs its listener the first time the event fires and then removes itself.

Are EventEmitter listeners called synchronously or asynchronously?

  • Synchronously, in registration order, before emit returns
  • Asynchronously on the next tick
  • In random order
  • Only after a setTimeout

Answer: Synchronously, in registration order, before emit returns. emit calls every listener right away, in the order they were registered, before it returns.

What happens if you emit 'error' with no listener registered for it?

  • Nothing happens
  • Node throws and the process exits
  • It logs a warning and continues
  • It retries the emit

Answer: Node throws and the process exits. The 'error' event is special: emitting it with no listener throws and crashes the process.

What does MaxListenersExceededWarning usually indicate?

  • The emitter is too fast
  • An event name is misspelled
  • Listeners are being added in a loop and never removed
  • The process is out of memory

Answer: Listeners are being added in a loop and never removed. By default more than 10 listeners on one event warns of a likely leak from listeners never removed.

To remove a listener with .off(), what must you pass?

  • The event name only
  • Any function with the same body
  • A new arrow function
  • The exact same function reference you registered

Answer: The exact same function reference you registered. .off() needs the same function reference, which is why you name listeners you plan to remove.

How do you give your own class .on() and .emit()?

  • class Logger extends EventEmitter
  • class Logger implements EventEmitter
  • new EventEmitter(Logger)
  • EventEmitter.call(Logger)

Answer: class Logger extends EventEmitter. Extending EventEmitter gives your class all the emitter methods so it can announce its own events.

If you call emit('ready') before registering the matching .on('ready', ...), what happens?

  • Node buffers the event and runs the listener later
  • The listener silently never runs
  • It throws an error
  • It runs the listener twice

Answer: The listener silently never runs. emit only calls listeners that already exist, so emitting before subscribing means the listener never runs.

Continue this course

Frequently asked questions

What is the difference between .on() and .once()?

Both subscribe a listener to an event. .on() keeps the listener attached, so it runs every single time the event is emitted. .once() runs the listener the first time the event fires and then automatically removes it, so it never runs again. Use .once() for one-shot things like a 'ready' or 'connected' signal that only happens a single time.

Why does emitting an 'error' event sometimes crash my Node program?

The 'error' event is treated specially by EventEmitter. If you emit 'error' and there is no listener registered for it, Node throws the error and, because nothing handled it, the process exits. The fix is to always attach an emitter.on('error', handler) listener to any emitter that might emit errors, so the error is handled gracefully instead of taking down your program.

What does MaxListenersExceededWarning mean?

By default each EventEmitter warns if you add more than 10 listeners for the same event, because that often signals a leak where listeners are being added in a loop and never removed. It is only a warning, not an error. If you genuinely need more, call emitter.setMaxListeners(n) to raise the limit, but first check that you are not forgetting to call .off() to clean up old listeners.

Are EventEmitter listeners synchronous or asynchronous?

Listeners run synchronously. When you call emitter.emit('event'), Node calls every listener for that event right away, one after another, in the order they were registered, before emit returns. If a listener needs to do async work, it can start it (for example with a promise or setImmediate), but emit itself does not wait for that work to finish.