How to fix Error: Unhandled error event (no error handler) in Node.js
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.
Quick fix
Fixes ERR_UNHANDLED_ERROR
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);- Attach error event listener before any operations
- Handle errors on child processes
- Handle errors on HTTP client requests
- Create custom EventEmitter subclasses with error handling
const stream = fs.createReadStream('file.txt');// WRONG - error may fire before listener is attachedstream.pipe(destination);stream.on('error', (err) => {console.error('Stream error:', err);});// CORRECT - attach error handler firststream.on('error', (err) => {console.error('Stream error:', err);});stream.pipe(destination);Error: Unhandled error event (no error handler)
What this error means
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.
Typical symptoms
- Process crashes unexpectedly with "Error: Unhandled error event" message
- Error happens at unpredictable times after application starts
- Works in development but crashes in production under certain load conditions
- Stream operations fail and crash the entire Node.js process
- Child process errors cause parent process to exit
- HTTP requests fail and terminate the application
- No stack trace or clear indication of which event emitter caused the issue
- Works when code is run with a debugger but crashes in normal execution
Common causes
- Error handler not attached to EventEmitter before error is emitted
- Error handler is attached after the error event is emitted (race condition)
- Stream operations fail but error handler only listening on "error" event of wrong stream
- Child process spawned without error event handler on the ChildProcess object
- Event emitter subclass does not initialize event handlers in constructor
- Removing error handler (using removeListener) before all errors are processed
- Async code emitting error events before await completes
- Custom EventEmitter subclass emitting error before super() initializes listeners
How to fix "Error: Unhandled error event (no error handler)"
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);Advanced notes
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
Frequently asked questions
- What does "ERR_UNHANDLED_ERROR" mean?
- js EventEmitters treat "error" events specially. 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.
- How do I fix "ERR_UNHANDLED_ERROR"?
- 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.
- What causes "ERR_UNHANDLED_ERROR"?
- Error handler not attached to EventEmitter before error is emitted Error handler is attached after the error event is emitted (race condition) Stream operations fail but error handler only listening on "error" event of wrong stream
- Is the "ERR_UNHANDLED_ERROR" error serious?
- Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.
Official resources & further reading
- 📚Node.js Official Documentation
- 🔗
- 🐙github.com - 44884(GitHub)
- 🐙github.com - 54670(GitHub)
- 🔗
- 🔗