Events and Utilities
By Syncfusion®, Inc. · Last updated
The observer pattern and EventEmitter: on, once, off, emit, prependListener, listener helpers, synchronous emit, arguments, the error event, captureRejections, listener limits, extending EventEmitter, events.once and events.on. Error-first callbacks, the util module (promisify, callbackify, inspect, format, types, isDeepStrictEqual, deprecate), cancelling work with AbortController and AbortSignal, and AsyncLocalStorage.
Events and Utilities
Event = a message that says "something happened", such as "the bell rang".
EventEmitter = a Node.js object that sends events and calls the functions that listen for them.
- Many Node.js objects are event emitters: servers, streams and
process. - One part of a program emits an event.
- Other parts listen and react.
- The two parts do not need to know each other.
Simple lesson: events let one part of a program announce news, and other parts react to it.
A Real-World Example
Think of the school bell.
- The bell rings once.
- Class 6 goes to lunch. Class 7 goes to the playground. The office closes the gate.
- The bell does not call each class one by one.
- Each class listens for the bell and knows what to do.
The bell is the emitter. Each class is a listener. "Ring" is the event name.
What You Will Learn
In this lesson, you will learn:
- The observer pattern and
EventEmitter on,once,off,emitand the listener helpers- Why
emitis synchronous, and how to pass data - The special
errorevent and listener limits - Making your own class from
EventEmitter events.once()andevents.on()withawait- Error-first callbacks and
util.promisify - The
utiltoolbox - Cancelling work with
AbortController, andAsyncLocalStorage
EventEmitter
Observer pattern = one object keeps a list of listeners and tells all of them when something happens.
on(name, fn)= add a listener.once(name, fn)= add a listener that runs only the first time.off(name, fn)= remove a listener.removeListeneris the same.emit(name, ...data)= send the event. Returnstrueif anyone was listening.prependListener(name, fn)= add a listener at the front of the line.
Click ▶ Run and read the terminal.
- Order: the prepended listener ran first. The rest ran in the order they were added.
- once: the office listener removed itself after one call, so 3 listeners are left.
- Data:
"lunch"was passed to every listener as an argument.
Listener Helpers
listenerCount(name)= how many listeners an event has.listeners(name)= the list of listener functions.eventNames()= all event names that have listeners.removeAllListeners(name)= remove every listener for that name.
Synchronous Emit
emit is synchronous. It calls every listener before the next line runs.
- Events are not a queue. Nothing waits for the event loop.
- A slow listener slows down the code that called
emit.
EventEmitter is a Node.js tool. Browser code uses addEventListener on page elements instead.
Simple lesson: on adds a listener, emit calls all listeners at once, in order.
Animation: emit sends one event to every listener of that name, in the order they were added bell.emit("ring")the emitter 1. students stand up 2. teacher starts class 3. log "ring"
One emit, many listeners. They run one after another, in the order you added them.
The Special error Event
- An event named
erroris special. - If you emit
errorand nobody listens, Node.js throws the error and the program crashes. - Always add an
errorlistener to emitters that can fail, such as streams and servers.
captureRejections
- An
asynclistener returns a promise. If it fails,emitdoes not notice. new EventEmitter({ captureRejections: true })sends such failures to theerrorevent.
Listener Limits and Memory Leaks
- By default, one event may have up to 10 listeners.
- An 11th listener prints a
MaxListenersExceededWarning. - This usually means code adds a listener again and again, for example inside a loop. That is a memory leak.
- If you really need more, call
emitter.setMaxListeners(20).
To remove a listener, pass the same function you added. Keep it in a named variable.
Simple lesson: always listen for error, and remove listeners you no longer need.
Extending EventEmitter
Your own classes can send events by extending EventEmitter.
Animation: a Classroom object emits a joined event and three listeners react one after another
Classroom emit("joined", "Asha") Register: mark present Teacher: say hello Counter: count + 1
One emit, three listeners, called one after another.
events.once() and events.on()
events.once(emitter, name)= a promise that waits for one event. Use it withawait.events.on(emitter, name)= an async iterator. Use it withfor awaitto handle many events.
Simple lesson: extend EventEmitter to give your own objects events, and await once() to wait for one.
Callback Conventions
Error-first callback = a callback whose first argument is an error, or null when all went well.
fs.readFile("marks.txt", "utf8", (err, data) => {
if (err) {
// handle the problem
return;
}
// use data
});
- Older Node.js APIs use callbacks.
- Newer APIs return promises, such as
node:fs/promises. - Most callback APIs have a promise twin. Prefer the promise version in new code.
fs/promises, timers/promises, stream/promises, readline/promises and dns/promises are the promise versions of their modules.
Simple lesson: in a callback API, always check err first.
The util Module
node:util is a toolbox of small helpers.
promisify(fn)= turn an error-first callback function into a function that returns a promise.callbackify(fn)= the opposite: turn an async function into a callback function.inspect(value)= the textconsole.logwould print for a value.format("%s has %d", a, b)= fill in a text template.types= checks such asutil.types.isPromise(x).isDeepStrictEqual(a, b)= are two objects equal, inside and out?deprecate(fn, message)= wrap an old function so it prints a warning once.
Simple lesson: reach for util before writing your own helper, especially promisify.
Cancelling Operations
AbortController = a remote control that can cancel work.
const ac = new AbortController()makes the controller.ac.signal= the signal you pass to the work.ac.abort()= press cancel. The work stops with anAbortError.AbortSignal.timeout(ms)= a signal that cancels by itself aftermsmilliseconds.- Many Node.js APIs take
{ signal }:timers/promises,events.once,fs/promises,child_process,httpandfetch.
Simple lesson: pass a signal to slow work, so you can cancel it.
AsyncLocalStorage (Advanced)
AsyncLocalStorage = a store that follows one chain of async work, such as one web request.
- A server handles many requests at the same time.
- You want every log line to show which request it belongs to.
- Passing a request id through every function is tiring.
als.run(store, fn)starts a chain.als.getStore()reads the store anywhere inside it, even afterawait.- Common uses: request ids in logs, the current user, timing.
// main.mjs
import { AsyncLocalStorage } from "node:async_hooks";
import { setTimeout as wait } from "node:timers/promises";
const als = new AsyncLocalStorage();
function log(msg) {
console.log(`[request ${als.getStore().id}] ${msg}`);
}
async function handle(id, delay) {
await als.run({ id }, async () => {
log("started");
await wait(delay);
log("finished");
});
}
await Promise.all([handle(1, 30), handle(2, 10)]);
Output:
[request 1] started
[request 2] started
[request 2] finished
[request 1] finished
- Request 2 finished first, but each line still shows the right id.
Simple lesson: AsyncLocalStorage remembers "which request am I in" across await.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
No error listener | An unhandled error event is ignored | Always add on("error", ...) | The program crashes |
| Adding a listener inside a loop or a request | Listeners clean themselves up | Add once, or remove with off | Memory leak and MaxListenersExceededWarning |
| Removing a listener with a new arrow function | off matches by code | Keep a name for the function and pass the same one | The listener is never removed |
Expecting emit to wait for the event loop | Events are queued | emit calls listeners at once | Code runs in a surprising order |
Ignoring err in a callback | err is only for logging | Check err first and return | Code uses undefined data |
Using promisify on a function without an error-first callback | It works on any callback | Only for (err, value) callbacks | The promise never settles, or the order is wrong |
TL;DR
EventEmitter = sends named events to listeners.
onadd,onceone time,offremove,emitsend
emitis synchronous = listeners run before the next line.errorevent = crashes the program if nobody listens.- Listener limit = 10 by default. More usually means a leak.
events.once= await one event.- Error-first callback =
(err, value). Checkerrfirst. util.promisify= callback function to promise function.- AbortController = cancel work through a
signal.
Check Yourself
Exercises
What to Remember
- An
EventEmittercalls its listeners at once, in order, when youemit. oncelistens one time.offneeds the same function you added.- Always handle the
errorevent. - Extend
EventEmitterto give your own classes events. util.promisifyturns old callbacks into promises.- Pass an
AbortSignalto slow work so it can be cancelled.
Next: Error Handling. You will read error messages, handle failures and decide when a program should stop.