This error occurs when an EventEmitter instance emits an "error" event but no listener is registered to handle it. Node.js will throw an unhandled error, crashing the process, because error events have special handling requirements in the EventEmitter API.
Node.js EventEmitters treat "error" events specially. Unlike other events, if an "error" event is emitted and no listener is attached to handle it, Node.js throws the error as an uncaught exception, terminating the entire process. This is by design: error events represent exceptional conditions that should not be silently ignored. If your code emits an error event, you must either: 1. Attach an error handler before the error is emitted 2. Use try-catch (if using async/await with promisified emitters) 3. Prevent the error condition from occurring The error commonly occurs with streams (readable, writable, duplex), child processes, HTTP clients, and custom EventEmitter subclasses. Any code that emits error events must have protection for handling them.
Always attach an error handler immediately after creating or obtaining an EventEmitter:
const stream = fs.createReadStream('file.txt');
// WRONG - error may fire before listener is attached
stream.pipe(destination);
stream.on('error', (err) => {
console.error('Stream error:', err);
});
// CORRECT - attach error handler first
stream.on('error', (err) => {
console.error('Stream error:', err);
});
stream.pipe(destination);For streams, attach error handlers to ALL streams involved:
const fs = require('fs');
const readable = fs.createReadStream('source.txt');
const writable = fs.createWriteStream('dest.txt');
// Handle errors on both streams
readable.on('error', (err) => {
console.error('Read error:', err);
});
writable.on('error', (err) => {
console.error('Write error:', err);
});
readable.pipe(writable);Child process objects require explicit error handling:
const { spawn } = require('child_process');
const child = spawn('some-command', ['arg1', 'arg2'']);
// WRONG - missing error handler
child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
// CORRECT - add error handler
child.on('error', (err) => {
console.error('Child process error:', err);
});
child.on('exit', (code) => {
console.log(`Process exited with code ${code}`);
});
child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});Handle stdio stream errors separately:
const child = spawn('command');
// Process error
child.on('error', (err) => {
console.error('Process error:', err.message);
});
// Stream errors
child.stdout.on('error', (err) => {
console.error('stdout error:', err);
});
child.stderr.on('error', (err) => {
console.error('stderr error:', err);
});
child.stdin.on('error', (err) => {
console.error('stdin error:', err);
});HTTP request objects are EventEmitters that can emit error events:
const http = require('http');
// WRONG - missing error handler
const req = http.get('http://example.com', (res) => {
res.on('data', (chunk) => {
console.log(chunk);
});
});
// CORRECT - handle request errors
const req = http.get('http://example.com', (res) => {
res.on('data', (chunk) => {
console.log(chunk);
});
res.on('error', (err) => {
console.error('Response error:', err);
});
});
req.on('error', (err) => {
console.error('Request error:', err.message);
});Using modern async/await:
const https = require('https');
const req = https.request('https://api.example.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => console.log(data));
res.on('error', (err) => console.error('Response error:', err));
});
req.on('error', (err) => {
console.error('Request error:', err.message);
});
req.write(JSON.stringify({ key: 'value' }));
req.end();When extending EventEmitter, ensure error handlers are set up in the constructor:
const EventEmitter = require('events');
// WRONG - error emitted without handler
class DataProcessor extends EventEmitter {
constructor() {
super();
}
process(data) {
if (!data) {
this.emit('error', new Error('No data provided'));
}
}
}
const processor = new DataProcessor();
processor.process(null); // Crashes!
// CORRECT - error handler attached in constructor
class DataProcessor extends EventEmitter {
constructor() {
super();
this.on('error', (err) => {
console.error('Processing error:', err.message);
});
}
process(data) {
if (!data) {
this.emit('error', new Error('No data provided'));
} else {
this.emit('success', data);
}
}
}
const processor = new DataProcessor();
processor.process(null); // Handled gracefullyConvert EventEmitter error handling to promises for cleaner async/await code:
const fs = require('fs');
const { promisify } = require('util');
const pipeline = promisify(require('stream').pipeline);
async function processFile() {
try {
const readable = fs.createReadStream('input.txt');
const writable = fs.createWriteStream('output.txt');
await pipeline(readable, writable);
console.log('File processed successfully');
} catch (err) {
console.error('Pipeline error:', err.message);
}
}
processFile();For custom EventEmitters:
function emitterToPromise(emitter, successEvent = 'success', errorEvent = 'error') {
return new Promise((resolve, reject) => {
emitter.once(successEvent, resolve);
emitter.once(errorEvent, reject);
});
}
try {
const result = await emitterToPromise(processor);
console.log('Result:', result);
} catch (err) {
console.error('Error:', err.message);
}Make sure error handlers are on the right EventEmitter object:
const fs = require('fs');
// WRONG - error handler on writable, but error comes from readable
const readable = fs.createReadStream('input.txt');
const writable = fs.createWriteStream('output.txt');
writable.on('error', (err) => {
console.error('Error:', err);
});
readable.pipe(writable); // Error on readable won't be caught
// CORRECT - handle both
readable.on('error', (err) => {
console.error('Read error:', err);
});
writable.on('error', (err) => {
console.error('Write error:', err);
});
readable.pipe(writable);For HTTP requests:
const http = require('http');
const req = http.get(url, (res) => {
res.on('error', (err) => {
console.error('Response stream error:', err);
});
});
req.on('error', (err) => {
console.error('Request error:', err);
});Check if error handlers exist before emitting:
class EventEmitterWithDefense extends EventEmitter {
emitError(error) {
if (this.listenerCount('error') > 0) {
this.emit('error', error);
} else {
console.error('Unhandled error in', this.constructor.name, ':', error);
}
}
}
const emitter = new EventEmitterWithDefense();
emitter.emitError(new Error('Something went wrong')); // Won't crashAlternatively, set a default error handler:
function safeEmitter(emitter) {
if (emitter.listenerCount('error') === 0) {
emitter.on('error', (err) => {
console.error('Unhandled error event:', err.message);
});
}
return emitter;
}
const stream = fs.createReadStream('file.txt');
safeEmitter(stream);Understanding Error Event Semantics
The "error" event is special because Node.js throws an uncaught error if it's emitted with no listeners. This is intentional: error events represent exceptional conditions and should never be silently ignored.
Race Conditions with Async Operations
Errors can be emitted asynchronously:
const stream = fs.createReadStream('file.txt');
setImmediate(() => {
stream.on('error', (err) => {
console.error(err);
});
});
stream.pipe(destination); // Error may fire immediately
// Solution - attach handler synchronously
stream.on('error', (err) => {
console.error(err);
});
setImmediate(() => {
stream.pipe(destination);
});Memory Leaks from Error Handlers
Attaching error handlers without cleanup can cause memory leaks:
// CORRECT - clean up handlers
function processStream(filePath) {
const stream = fs.createReadStream(filePath);
const errorHandler = (err) => {
console.error(err);
stream.removeListener('error', errorHandler);
};
stream.on('error', errorHandler);
return stream;
}EventEmitter Subclasses
When extending EventEmitter, decide whether errors should bubble up:
class DataFetcher extends EventEmitter {
async fetch(url) {
try {
const response = await fetch(url);
const data = await response.json();
this.emit('data', data);
} catch (err) {
if (this.listenerCount('error') > 0) {
this.emit('error', err);
} else {
throw err;
}
}
}
}Best Practices
- Always attach error handlers synchronously before operations
- Use promises/async-await for cleaner error handling
- Check listenerCount('error') before emitting errors
- Clean up event listeners to prevent memory leaks
- Use removeListener() or removeAllListeners() when appropriate
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process