This error occurs when code attempts to interact with a listener that was already invoked through the once() method. The once() method registers a one-time listener that automatically removes itself after the first event emission, causing issues if code tries to access it again.
The error "Listener already called" or "once event already fired" indicates an attempt to use or reference a listener that was registered with the EventEmitter's once() method and has already been invoked. The once() method in Node.js registers a listener that executes exactly once. When the event is emitted, the listener is automatically removed from the EventEmitter's internal listener array before being invoked. This design pattern means the listener cannot fire again, even if the same event is emitted multiple times. This error typically occurs when code doesn't account for the one-time nature of once() listeners, or when trying to perform operations on listeners after they've already been fired. It's a logic error rather than a system error, resulting from incorrect assumptions about listener behavior.
The key difference is listener persistence:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
// Using on() - listener persists
emitter.on('data', (value) => {
console.log('Received:', value);
});
emitter.emit('data', 'first'); // Prints: Received: first
emitter.emit('data', 'second'); // Prints: Received: second
emitter.emit('data', 'third'); // Prints: Received: third
// Using once() - listener fires once then auto-removes
emitter.once('ready', () => {
console.log('Ready!');
});
emitter.emit('ready'); // Prints: Ready!
emitter.emit('ready'); // Prints nothing (listener already removed)The once() listener is automatically unregistered after the first emission. It doesn't persist like on() listeners do.
Do not attempt to call removeListener() or other manipulation methods on listeners registered with once() after the event fires:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
function handleOnce() {
console.log('One-time handler');
}
// Register as one-time listener
emitter.once('event', handleOnce);
emitter.emit('event'); // Prints: One-time handler
// WRONG: Trying to remove after it's already fired and auto-removed
emitter.removeListener('event', handleOnce); // No-op, already removed
emitter.listenerCount('event'); // Returns 0
// CORRECT: If you need to control removal, use on() instead
emitter.on('event', handleOnce);
// Then later, explicitly remove when needed:
emitter.removeListener('event', handleOnce);If you need to unregister a listener yourself, use on() instead of once() and manage removal explicitly.
Design your code with once() for events that truly happen once. For operations that might need control over lifecycle, use on() with explicit removal:
const { EventEmitter } = require('events');
const server = new EventEmitter();
// Correct use: Server startup happens once
server.once('listening', () => {
console.log('Server started on port 3000');
});
// Correct use: Handling multiple connections
server.on('connection', (socket) => {
console.log('Client connected:', socket.id);
});
// DO NOT do this - it defeats the purpose of once()
let shouldHandle = true;
server.once('data', () => {
if (shouldHandle) {
console.log('Processing data');
shouldHandle = false; // Unnecessary, once() already does this
}
});Use once() for startup events, initial handshakes, and one-time state transitions. Use on() for ongoing events.
If you need to verify whether a listener is still registered, check the listener count:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
function handleReady() {
console.log('Ready!');
}
emitter.once('ready', handleReady);
console.log('Listeners before emit:', emitter.listenerCount('ready')); // 1
emitter.emit('ready');
console.log('Listeners after emit:', emitter.listenerCount('ready')); // 0
// Safe approach: Check before operating on listeners
if (emitter.listenerCount('ready') > 0) {
// Do something with active listeners
} else {
console.log('No active listeners for this event');
}This prevents errors from assuming a listener still exists after its automatic removal.
If you need a one-time listener that executes first (before other listeners), use prependOnceListener():
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
// Regular once() - executes in registration order
emitter.once('event', () => console.log('a'));
emitter.on('event', () => console.log('b'));
// Prepend once() - executes before others
emitter.prependOnceListener('event', () => console.log('c'));
emitter.emit('event');
// Prints:
// c
// a
// b
// After first emit, the once() listeners are removed
emitter.emit('event');
// Prints only:
// bThe prepended listener still follows once() semantics - it only fires once, then auto-removes.
If you need a one-time listener to be available for repeated events, re-register it after each emission:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
function registerOneTimeHandler() {
emitter.once('attempt', (result) => {
console.log('Attempt result:', result);
if (result === 'retry') {
// Re-register for next attempt
registerOneTimeHandler();
}
});
}
// Initial registration
registerOneTimeHandler();
emitter.emit('attempt', 'failed'); // Fires, prints: Attempt result: failed
emitter.emit('attempt', 'success'); // Does nothing (listener already removed)
// Need to call registerOneTimeHandler() again for next batch
registerOneTimeHandler();
emitter.emit('attempt', 'success'); // Fires againThis pattern lets you control when the one-time listener becomes active again.
Memory Implications of once() vs on()
The once() method is more memory-efficient for events that truly happen once, because the listener is automatically removed. Using on() without explicit removal can cause memory leaks if listeners accumulate:
// Memory leak risk - on() listeners accumulate
for (let i = 0; i < 1000; i++) {
emitter.on('event', () => {
// This listener never gets removed
});
}
console.log(emitter.listenerCount('event')); // 1000 listeners
// Better - once() auto-removes
for (let i = 0; i < 1000; i++) {
emitter.once('event', () => {
// Each listener auto-removes after firing
});
}
emitter.emit('event');
console.log(emitter.listenerCount('event')); // 0 listenersEvent Listener Management Patterns
For complex applications with many listeners, consider these patterns:
1. Explicit removal: Use on() with careful tracking of removal points
2. Scope-based: Wrap listeners in closures that manage their own lifecycle
3. Once for initialization: Use once() for setup events, on() for ongoing operations
4. Middleware pattern: Create listener factories that handle both registration and cleanup
class ListenerManager {
constructor(emitter) {
this.emitter = emitter;
this.listeners = new Map();
}
registerOnce(event, handler) {
const wrappedHandler = (...args) => {
handler(...args);
this.listeners.delete(`${event}:${handler.name}`);
};
this.emitter.once(event, wrappedHandler);
this.listeners.set(`${event}:${handler.name}`, wrappedHandler);
}
removeListener(event, handlerName) {
const key = `${event}:${handlerName}`;
if (this.listeners.has(key)) {
this.emitter.removeListener(event, this.listeners.get(key));
this.listeners.delete(key);
}
}
listActive(event) {
return Array.from(this.listeners.keys()).filter(k => k.startsWith(`${event}:`));
}
}Promise-based Alternatives
Modern Node.js code often uses promises or async/await instead of once() for better readability:
const { once } = require('events');
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
async function waitForReady() {
const [value] = await once(emitter, 'ready');
console.log('Ready! Value:', value);
// Can only be called once, naturally
}
waitForReady();
emitter.emit('ready', 'initialized');The events.once() utility function wraps once() behavior in a promise, which combines with async/await for cleaner code.
Debugging once() Listener Issues
Enable EventEmitter debug output to trace listener behavior:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
// Show all listener changes
emitter.on('newListener', (event, listener) => {
console.log('Listener added:', event, listener.name || 'anonymous');
});
emitter.on('removeListener', (event, listener) => {
console.log('Listener removed:', event, listener.name || 'anonymous');
});
function myHandler() { }
emitter.once('test', myHandler); // Logs: Listener added
emitter.emit('test'); // Logs: Listener removedThis helps identify unexpected listener lifecycle issues in complex applications.
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams
Error: setuid EPERM (operation not permitted, user change failed)
setuid EPERM: Operation not permitted when changing process user