Fix: Node.js Crashing with UnhandledPromiseRejection (--unhandled-rejections)
Quick Answer
How to fix Node.js UnhandledPromiseRejectionWarning and process crashes — why unhandled promise rejections crash Node.js 15+, how to add global handlers, find the source of the rejection, and fix async error handling.
The Error
Node.js exits with an unhandled promise rejection error:
node:internal/process/promises:289
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an
async function without a catch block, or by rejecting a promise which was not
handled with .catch(). The promise rejected with the reason:
Error: connect ECONNREFUSED 127.0.0.1:5432]Or in older Node.js versions — a warning instead of a crash:
(node:1234) UnhandledPromiseRejectionWarning: Error: connect ECONNREFUSED
(node:1234) UnhandledPromiseRejectionWarning: Unhandled promise rejection.
This error originated either by throwing inside of an async function without
a catch block, or by rejecting a promise which was not handled with .catch().Why This Happens
In Node.js 15+, an unhandled promise rejection crashes the process with exit code 1. In older versions it was a warning. The rejection is “unhandled” when:
- An
asyncfunction throws but is notawaited — calling an async function withoutawaitmeans its rejection has no.catch()handler. - A
.then()chain has no.catch()— any rejection in the chain propagates until it reaches a.catch(). Without one, it’s unhandled. Promise.all()orPromise.allSettled()has a rejecting promise — if thePromise.all()call itself is notawaited or not followed by.catch(), the rejection is unhandled.- Event emitter callbacks that are async —
EventEmitterdoesn’t understand promises. If an async listener throws, the rejection is unhandled. - Express route handlers not calling
next(err)— unhandled async errors in Express 4 don’t automatically reach the error handler.
Fix 1: Add try/catch to Every async Function
// Wrong — async function called without await or .catch()
async function loadData() {
const data = await db.query('SELECT * FROM users'); // Can throw
return data;
}
// Called but not awaited — rejection is unhandled
loadData(); // ✗ Fire and forget — any error is swallowed or unhandled
// Correct — always await or handle the rejection
async function main() {
try {
const data = await loadData(); // ✓ Awaited — any rejection is caught here
console.log(data);
} catch (err) {
console.error('Failed to load data:', err);
process.exit(1); // Or handle gracefully
}
}
main(); // Top-level call — still fire-and-forget but errors are caught insideWrap all top-level async code:
// Pattern for Node.js scripts
async function main() {
// All your async code here — errors caught and handled
const db = await connectToDatabase();
await runMigrations(db);
await startServer(db);
}
main().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});Fix 2: Fix Promise Chains Without .catch()
// Wrong — no .catch() on the chain
fetch('https://api.example.com/data')
.then(res => res.json())
.then(data => processData(data));
// If fetch fails or processData throws, rejection is unhandled
// Correct — add .catch()
fetch('https://api.example.com/data')
.then(res => res.json())
.then(data => processData(data))
.catch(err => {
console.error('Request failed:', err);
});
// Or use async/await for cleaner error handling
async function fetchData() {
try {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return processData(data);
} catch (err) {
console.error('Request failed:', err);
throw err; // Re-throw if caller needs to handle it
}
}Fix 3: Fix Async Event Listeners
EventEmitter does not understand promises. If an async listener rejects, the error is unhandled:
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Wrong — async listener rejection is unhandled
emitter.on('data', async (payload) => {
await processPayload(payload); // If this throws, nobody catches it
});
// Correct — wrap in try/catch
emitter.on('data', async (payload) => {
try {
await processPayload(payload);
} catch (err) {
console.error('Error processing payload:', err);
emitter.emit('error', err); // Route to error handler
}
});
// For EventEmitter errors — always add an error listener
emitter.on('error', (err) => {
console.error('Emitter error:', err);
});For streams and other Node.js built-in EventEmitters:
const { pipeline } = require('stream/promises');
// Use the promise-based pipeline to handle stream errors
async function processStream(readable, writable) {
try {
await pipeline(readable, transform, writable);
} catch (err) {
console.error('Stream pipeline failed:', err);
}
}Fix 4: Fix Express Async Route Handlers
Express 4 does not automatically catch async errors — you must either call next(err) or use a wrapper:
// Wrong — async error not passed to Express error handler
app.get('/users', async (req, res) => {
const users = await db.getUsers(); // If this throws, Express doesn't catch it
res.json(users);
});
// Fix A — wrap manually
app.get('/users', async (req, res, next) => {
try {
const users = await db.getUsers();
res.json(users);
} catch (err) {
next(err); // Passes to Express error handling middleware
}
});
// Fix B — use a wrapper function
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/users', asyncHandler(async (req, res) => {
const users = await db.getUsers();
res.json(users);
}));
// Fix C — Express 5 (handles async errors automatically)
// npm install express@5
app.get('/users', async (req, res) => {
const users = await db.getUsers(); // Express 5 catches this automatically
res.json(users);
});Express error handling middleware:
// Must be defined after all routes
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message,
});
});Fix 5: Add a Global Unhandled Rejection Handler
As a safety net — catch any rejections that slip through:
// Catch unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise);
console.error('Reason:', reason);
// In production — log to error tracking service
// Sentry.captureException(reason);
// Graceful shutdown
process.exit(1); // Node.js 15+ exits anyway, but be explicit
});
// Catch synchronous uncaught exceptions
process.on('uncaughtException', (err, origin) => {
console.error('Uncaught Exception:', err);
console.error('Origin:', origin);
// Always exit after uncaughtException — the process state is unreliable
process.exit(1);
});
// Graceful shutdown on SIGTERM (e.g., from Docker or Kubernetes)
process.on('SIGTERM', async () => {
console.log('SIGTERM received — shutting down gracefully');
await server.close();
await db.disconnect();
process.exit(0);
});Warning: Do not use
unhandledRejectionto swallow errors and continue running. After an unhandled rejection, the process may be in an inconsistent state. Log the error, then either exit or restart via a process manager (PM2, systemd).
Fix 6: Find the Source of the Unhandled Rejection
The error message often doesn’t show which line of your code caused the rejection. Use these techniques to find it:
Enable long stack traces:
# Node.js built-in — shows async stack traces
node --stack-trace-limit=50 server.js
# Or with the --enable-source-maps flag for TypeScript
node --enable-source-maps server.jsUse the --trace-warnings flag:
node --trace-warnings server.js
# Shows the full stack trace for every warning including UnhandledPromiseRejectionIntercept rejections before they become unhandled:
// Monkey-patch Promise to track unhandled rejections
const originalPromise = global.Promise;
global.Promise = class TrackedPromise extends originalPromise {
constructor(executor) {
super(executor);
// Add a no-op .catch to prevent "unhandled" but log when it fires
this.catch((err) => {
console.trace('Promise rejected without handler:', err);
});
}
};Or use the --unhandled-rejections flag to control behavior:
# Node.js 15+ options for --unhandled-rejections:
# 'throw' (default in Node 15+) — throws an exception, crashes the process
# 'warn' (Node 14 default) — logs a warning, process continues
# 'none' — silently ignores (never use in production)
# 'warn-with-error-code' — warns and sets exit code
node --unhandled-rejections=warn server.js # Temporarily use Node 14 behaviorFix 7: Fix Timer and setInterval Async Handlers
// Wrong — async callback in setInterval is not awaited
setInterval(async () => {
await syncData(); // If this throws, the rejection is unhandled
}, 60_000);
// Correct — wrap in try/catch
setInterval(async () => {
try {
await syncData();
} catch (err) {
console.error('Sync failed:', err);
// Do NOT rethrow — setInterval will continue on the next tick
}
}, 60_000);
// Better — use a self-scheduling async function for control over timing
async function scheduledSync() {
while (true) {
try {
await syncData();
} catch (err) {
console.error('Sync failed:', err);
}
await new Promise(resolve => setTimeout(resolve, 60_000));
}
}
scheduledSync().catch(err => {
console.error('Scheduler crashed:', err);
process.exit(1);
});Still Not Working?
Check third-party libraries. Some older libraries return unhandled promises internally. Check the library’s issue tracker or upgrade to a newer version.
Use Node.js --inspect with Chrome DevTools to pause on unhandled rejections:
node --inspect-brk server.js
# Open chrome://inspect in Chrome
# DevTools → Sources → Event Listener Breakpoints → Promise → Unhandled RejectionUse why-is-node-running to find what’s keeping the process alive after an error:
npm install -g why-is-node-running
# Add to your entry point:
const why = require('why-is-node-running');
setTimeout(why, 5000); // After 5s, print what's keeping Node aliveFor related Node.js and async issues, see Fix: JavaScript Unhandled Promise Rejection and Fix: Python asyncio Runtime Error No Running Event Loop.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Express req.body Is undefined
How to fix req.body being undefined in Express — missing body-parser middleware, wrong Content-Type header, middleware order issues, and multipart form data handling.
Fix: TypeScript Decorators Not Working (experimentalDecorators)
How to fix TypeScript decorators not applying — experimentalDecorators not enabled, emitDecoratorMetadata missing, reflect-metadata not imported, and decorator ordering issues.
Fix: Socket.IO Not Connecting (CORS, Transport, and Namespace Errors)
How to fix Socket.IO connection failures — CORS errors, transport fallback issues, wrong namespace, server not emitting to the right room, and how to debug Socket.IO connections in production.
Fix: Stripe Webhook Signature Verification Failed
How to fix Stripe webhook signature verification errors — why Stripe-Signature header validation fails, how to correctly pass the raw request body, and how to debug webhook delivery in the Stripe dashboard.