emitter.once()
Register a listener that runs on the next emit only and then removes itself.
emitter.once(eventName, listener)- Added in Node.js v0.3.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$eventName | <string> | <symbol> | The name of the event. |
$listener | <Function> | The callback function, invoked at most once. |
Return value
<EventEmitter>A reference to the emitter, so calls can be chained.
Errors
Throws ERR_INVALID_ARG_TYPE if listener is not a function.
once() adds a one-shot listener. The next time eventName fires, Node removes the listener first and then invokes it. That ordering is the important detail: by the time your callback body runs, it is already detached, so re-emitting the same event from inside the handler cannot cause infinite recursion.
When to reach for it
Use it for lifecycle events that are meaningful exactly once per object: 'ready', 'listening', 'close', 'end', a settle signal from a connection. It is also the correct choice for cleanup handlers, because a self-removing listener can never leak.
What trips people up
The listener is registered under a wrapper. emitter.listeners() reports your original function, but if you need to cancel a pending one-shot before it fires, pass the same function you gave to once() to off() — Node matches the wrapper for you.
once() only fires on a future emit. Registering it after the event has already happened means it never runs. For the awaitable version of the same idea, use the module-level events.once().
Example
When to use it
- Run setup code the first time a connection emits 'ready' and never again
- Resolve a bootstrap step on the first 'listening' event from an HTTP server
- Attach a cleanup handler to 'close' that cannot leak because it removes itself
Frequently asked questions
What is the difference between on() and once() in Node.js?
on() keeps the listener registered for every emit; once() removes it just before the first invocation, so it runs at most one time. Use once() for lifecycle events like 'ready' or 'close' and on() for recurring ones like 'data'.
Can I remove a once() listener before it fires?
Yes — call emitter.off(eventName, listener) with the same function you passed to once(). Node stores your function inside a wrapper but off() matches it correctly, so no extra reference bookkeeping is needed.
Does re-emitting an event inside a once() handler cause infinite recursion?
No. The listener is removed before it is invoked, so the nested emit finds no once() listener left to call. This ordering is documented behaviour and makes once() safe for state-machine transitions that re-fire the same event.
More examples
Cancelling a one-shot before it fires
once() stores your function inside a wrapper that performs the self-removal, but off() knows how to unwrap it. Output shows the count going 1 then 0, and the emit produces nothing. This is how you abandon a pending one-shot when a timeout wins the race instead.
const { EventEmitter } = require('node:events');
const conn = new EventEmitter();
const onReady = (host) => console.log('never runs:', host);
conn.once('ready', onReady);
console.log('pending:', conn.listenerCount('ready'));
// Pass the ORIGINAL function; Node matches the internal wrapper.
conn.off('ready', onReady);
console.log('after off:', conn.listenerCount('ready'));
conn.emit('ready', 'db.internal');
console.log('done');Racing 'ready' against 'error'
Two competing one-shots settle a promise. Each removes the other on the way out, so the losing listener does not sit on the emitter forever holding the closure alive. events.once() does exactly this bookkeeping for you and is the shorter way to write it.
const { EventEmitter } = require('node:events');
function connect() {
const conn = new EventEmitter();
return new Promise((resolve, reject) => {
const onReady = (host) => { conn.off('error', onError); resolve(host); };
const onError = (err) => { conn.off('ready', onReady); reject(err); };
conn.once('ready', onReady);
conn.once('error', onError);
setTimeout(() => conn.emit('ready', 'db.internal'), 10);
});
}
connect().then((host) => console.log('resolved with', host));
Discussion