Fix: UnhandledPromiseRejectionWarning / UnhandledPromiseRejection
Quick Answer
How to fix UnhandledPromiseRejectionWarning in Node.js and unhandled promise rejection errors in JavaScript caused by missing catch handlers, async/await mistakes, and event emitter errors.
The Error
You run a Node.js script or browser app and see:
UnhandledPromiseRejectionWarning: Error: connect ECONNREFUSED 127.0.0.1:5432
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().Or in newer Node.js versions (v15+):
node:internal/process/promises:279
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]Or in the browser console:
Uncaught (in promise) TypeError: Failed to fetch
Uncaught (in promise) Error: Request failed with status code 401The error means a Promise rejected (failed) but nothing handled that rejection — there was no .catch(), no try/catch around await, and no rejection handler attached to the Promise.
Note: In Node.js v15+, an unhandled promise rejection crashes the process by default. In older versions it printed a warning but kept running. If your process is crashing unexpectedly after an upgrade, this is a likely cause.
Why This Happens
Every Promise can be in one of three states: pending, fulfilled, or rejected. When a Promise rejects — a network request fails, a database query errors, or you explicitly call reject() — Node.js or the browser looks for a rejection handler.
If none is found, the runtime emits unhandledRejection. Common causes:
- Missing
.catch()on a promise chain. awaitwithouttry/catchinside anasyncfunction.- Async function called without
await— the returned promise is ignored. - Event handlers that are
async— errors thrown inside are not caught by the caller. Promise.all()with no error handler — one rejection kills all.- Forgetting to
returna promise inside.then(), breaking the chain.
Fix 1: Add .catch() to Promise Chains
Every .then() chain needs a .catch() at the end:
Broken:
fetch("/api/data")
.then(res => res.json())
.then(data => console.log(data));
// If fetch fails, the rejection is unhandledFixed:
fetch("/api/data")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error("Fetch failed:", err));The .catch() must be at the end of the chain. Adding it only to the first .then() will not catch errors thrown in later .then() callbacks.
Return promises inside .then() to keep the chain intact:
// Broken — inner promise is orphaned
fetch("/api/users")
.then(res => {
fetch("/api/posts"); // Missing return — this promise is orphaned
})
.catch(err => console.error(err)); // Does NOT catch errors from /api/posts
// Fixed
fetch("/api/users")
.then(res => {
return fetch("/api/posts"); // Returning keeps it in the chain
})
.catch(err => console.error(err));Fix 2: Wrap await in try/catch
When using async/await, wrap calls that can fail in try/catch:
Broken:
async function loadUser(id) {
const res = await fetch(`/api/users/${id}`); // Can reject
const user = await res.json(); // Can also throw
return user;
}
loadUser(123); // No await, no catch — rejection goes unhandledFixed:
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();
return user;
} catch (err) {
console.error("Failed to load user:", err);
throw err; // Re-throw if the caller needs to handle it
}
}
// Always await or catch the async function's result
try {
const user = await loadUser(123);
} catch (err) {
// Handle here
}Pro Tip: A
try/catcharoundawaitcatches both Promise rejections and synchronous errors thrown inside the async function. It replaces both.catch()and a regulartry/catchin most cases.
Fix 3: Always await or catch async function calls
When you call an async function, it returns a Promise. If you do not await it or chain .catch(), any rejection is unhandled:
Broken:
async function sendEmail(to, subject) {
const result = await emailService.send(to, subject);
return result;
}
// In an event handler or fire-and-forget call:
button.addEventListener("click", () => {
sendEmail("[email protected]", "Welcome"); // Missing await AND .catch()
});Fixed — add .catch():
button.addEventListener("click", () => {
sendEmail("[email protected]", "Welcome")
.catch(err => console.error("Email failed:", err));
});Fixed — make the handler async:
button.addEventListener("click", async () => {
try {
await sendEmail("[email protected]", "Welcome");
} catch (err) {
console.error("Email failed:", err);
}
});Note: Express.js route handlers do not automatically catch async errors. Wrap them or use an async wrapper utility:
// Broken — Express doesn't catch async rejections automatically (Express 4)
app.get("/users", async (req, res) => {
const users = await db.getUsers(); // If this rejects, Express hangs
res.json(users);
});
// Fixed — wrap with try/catch
app.get("/users", async (req, res, next) => {
try {
const users = await db.getUsers();
res.json(users);
} catch (err) {
next(err); // Pass to Express error handler
}
});Express 5 (currently in beta) handles async errors automatically.
Fix 4: Handle Promise.all() Rejections
Promise.all() rejects as soon as any single Promise in the array rejects:
Broken:
async function loadDashboard() {
const [users, posts, stats] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchStats(), // If this rejects, the whole Promise.all rejects
]);
}
loadDashboard(); // No catchFixed:
async function loadDashboard() {
try {
const [users, posts, stats] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchStats(),
]);
return { users, posts, stats };
} catch (err) {
console.error("Dashboard load failed:", err);
throw err;
}
}If you want all results even if some fail, use Promise.allSettled():
const results = await Promise.allSettled([fetchUsers(), fetchPosts(), fetchStats()]);
results.forEach((result, i) => {
if (result.status === "fulfilled") {
console.log(`Request ${i} succeeded:`, result.value);
} else {
console.error(`Request ${i} failed:`, result.reason);
}
});Why this matters:
Promise.allSettled()always resolves with an array of outcome objects — it never rejects. Use it when partial failures are acceptable and you want all available data.
Fix 5: Add a Global Unhandled Rejection Handler
As a safety net — not a replacement for proper error handling — add a global handler to catch any rejections that slip through:
Node.js:
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled Rejection at:", promise, "reason:", reason);
// Log to error tracking service (Sentry, Datadog, etc.)
// Optionally exit: process.exit(1);
});Browser:
window.addEventListener("unhandledrejection", (event) => {
console.error("Unhandled promise rejection:", event.reason);
event.preventDefault(); // Suppress the browser's default console error
});Add this near the top of your entry file. It is useful for catching rejections you may have missed, but the root fix is always to add proper .catch() or try/catch to the originating code.
Fix 6: Fix Async Event Emitter Errors
EventEmitter callbacks in Node.js do not automatically propagate errors from async handlers:
Broken:
const EventEmitter = require("events");
const emitter = new EventEmitter();
emitter.on("data", async (data) => {
await processData(data); // If this rejects, the error is unhandled
});Fixed:
emitter.on("data", async (data) => {
try {
await processData(data);
} catch (err) {
emitter.emit("error", err); // Route to the error event
}
});
emitter.on("error", (err) => {
console.error("Emitter error:", err);
});Always add an "error" listener to EventEmitters. An unhandled "error" event in Node.js throws an exception.
Fix 7: Find All Unhandled Rejections in Your Codebase
Search your code for async patterns that lack error handling:
# Find async functions that might be called without await or catch
grep -rn "async function\|async (" src/ --include="*.js" --include="*.ts"
# Find .then() calls without a following .catch()
grep -rn "\.then(" src/ --include="*.js" | grep -v "\.catch("Use ESLint rules to enforce promise handling automatically:
{
"rules": {
"no-floating-promises": "error",
"@typescript-eslint/no-floating-promises": "error",
"promise/catch-or-return": "error"
}
}The @typescript-eslint/no-floating-promises rule catches unhandled async calls at lint time, before they become runtime errors. For related ESLint issues, see Fix: ESLint Parsing Error: Unexpected token.
Still Not Working?
Check for rejection in a constructor. You cannot use await in a constructor. If you call an async function inside a constructor without handling its result, you get an unhandled rejection. Move async initialization to a static factory method or an init() method called after construction.
Check third-party library callbacks. Some libraries accept callbacks that they call synchronously or asynchronously. If you pass an async callback and the library does not handle the returned Promise, rejections go unhandled. Wrap the callback body in try/catch and handle errors manually.
Check for race conditions with setTimeout or setInterval. Async functions called inside timers run outside any surrounding try/catch. Each timer callback needs its own error handling.
Node.js version matters. Node.js v14 and earlier printed a deprecation warning for unhandled rejections but continued running. Node.js v15+ crashes the process. If your app started crashing after a Node.js upgrade, search for all promise chains that lack .catch(). See Fix: node: cannot find module for other Node.js startup errors.
Use async stack traces. In Node.js, set --async-stack-traces (enabled by default in v12+) or in Chrome DevTools enable “Async” in the Call Stack panel. This shows you where the Promise was created, not just where it rejected — much easier to trace the origin.
For errors thrown inside .then() callbacks that look like TypeError: x is not a function, the unhandled rejection wraps the underlying TypeError — fix the inner error first.
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: Node.js Crashing with UnhandledPromiseRejection (--unhandled-rejections)
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.
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.