JavaScript UnhandledPromiseRejection — A rejected Promise was not caught
UnhandledPromiseRejectionWarning: Promise rejected with no handler
Verified against MDN Web Docs — Promise, Node.js 20 unhandledRejection docs, ECMAScript 2022 spec §27.2 · Updated June 2026
> quick_fix
Add a .catch() handler to every Promise chain, or wrap async/await code in a try-catch block. Every async operation that can fail must have an error handler.
// BROKEN: no error handling
async function loadUser(id) {
const user = await fetchUser(id); // throws if network fails
return user;
}
// FIXED option 1: try-catch in async function
async function loadUser(id) {
try {
const user = await fetchUser(id);
return user;
} catch (error) {
console.error('Failed to load user:', error);
return null; // or re-throw as a domain error
}
}
// FIXED option 2: .catch() on the Promise
fetchUser(id)
.then(user => processUser(user))
.catch(error => console.error('Failed:', error));
// Global handler for unhandled rejections (Node.js — last resort)
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
process.exit(1);
});What causes this error
UnhandledPromiseRejection occurs when a Promise is rejected (either explicitly via `Promise.reject()` or because an async function throws an error) and there is no `.catch()` handler or `try/catch` block around the `await` to handle the rejection. Common causes: network request failures with no error handling, an async function that throws but is called without await or .catch(), or a setTimeout/setInterval callback that returns a rejected Promise.
How to fix it
- 01
step 1
Find the source of the rejection
The warning includes the reason (the error thrown) and in Node.js 15+ it crashes the process. The stack trace in the 'reason' points to the async function that threw. Find that function and add error handling.
- 02
step 2
Add try-catch to every async function that can fail
Every `await` call that can throw (network requests, database queries, file system operations) should be inside a try-catch: `try { const data = await fetch(...); } catch(e) { ... }`. Missing try-catch around a single await is the most common cause.
- 03
step 3
Add .catch() to Promise chains
Every `.then()` chain must end with `.catch()`. A rejection in any step of the chain propagates to .catch(). Without .catch(), the rejection is unhandled. `somePromise.then(fn1).then(fn2).catch(handleError)` — the catch covers all previous steps.
- 04
step 4
Handle fire-and-forget async calls correctly
Calling `asyncFn()` without `await` (fire-and-forget) creates a Promise that is not being tracked. If it rejects, the rejection is unhandled. Either await it, or attach .catch(): `asyncFn().catch(err => logger.error(err))`.
- 05
step 5
Add a global handler as a safety net
In Node.js: `process.on('unhandledRejection', ...)`. In browsers: `window.addEventListener('unhandledrejection', ...)`. Use these to log and report unexpected rejections, but treat them as alerts that a specific catch is missing — not as the primary handler.
How to verify the fix
- Trigger the error condition (e.g., simulate a network failure) — no UnhandledPromiseRejection in console or logs
- Confirm the error path is handled gracefully (user sees an error state, not a blank screen)
- In Node.js, confirm the process does not crash on rejection
Why UnhandledPromiseRejection happens at the runtime level
JavaScript's event loop tracks Promises and their rejection handlers. When a Promise transitions to the rejected state, the engine checks if a rejection handler (via .catch() or the rejection argument of .then()) has been attached. If no handler is attached by the time the microtask queue is drained, the runtime emits an 'unhandledRejection' event. In Node.js 15+, no handler for this event causes the default behaviour: printing the stack trace and calling process.exit(1). The timing is based on microtask queue draining, which is why a rejection can be 'unhandled' even if you attach .catch() asynchronously after the rejection.
Common debug mistakes for UnhandledPromiseRejection
- Fire-and-forget async calls: calling `doSomethingAsync()` without await or .catch()
- Forgetting to await an async call inside a non-async function — the Promise is created but never tracked
- Rejecting inside a .then() callback that has no corresponding .catch()
- Using Promise.all() without a .catch() — a single rejection in any of the array members causes an unhandled rejection
When UnhandledPromiseRejection signals a deeper problem
Unhandled Promise rejections are a category of silent failure: the operation did not complete, but the code continued as if nothing happened. In UI code, this means users see an empty screen or stale data with no error message. In server-side code, it means a background job silently failed. The systemic fix is an architectural one: establish an error boundary convention (global handler for unexpected rejections, domain-specific try-catch for expected failures) and enforce it via ESLint's `no-floating-promises` rule (TypeScript) or `promise/catch-or-return` (JS).
Editor's take
Unhandled Promise Rejection is JavaScript's way of telling you that a Promise was rejected and nobody attached a `.catch()` handler or wrapped the `await` in a try-catch. In Node.js, starting from v15, unhandled rejections crash the process by default — this is intentional to prevent silent failures in production servers. In browsers, unhandled rejections log to the console but don't crash the page, which means they often go unnoticed during development and cause mysterious behavior in production. The most common cause is an async function that throws but is called without `await` — the rejection happens but the caller never observes it because they discarded the returned Promise. Another frequent pattern: `.then().then().catch()` chains where an error in the second `.then()` is caught, but if you refactor to separate `.then()` calls, the catch no longer covers both. In Express.js, async route handlers that throw without a wrapper silently hang the request with no response until the client times out. The fix is structural: either use `express-async-errors` or wrap every async handler. In React, unhandled rejections in event handlers don't trigger Error Boundaries — only synchronous errors in render do. For production Node services, always register a `process.on('unhandledRejection')` handler as a safety net, but treat each occurrence as a bug to fix, not an expected condition.
By Bikram Nath · Curator · Updated June 2026
Frequently asked questions
Why does Node.js crash on UnhandledPromiseRejection but browsers just warn?
Node.js 15+ changed the default behaviour from emitting a warning to crashing the process (exit code 1). This was intentional — silently swallowing rejections in server-side code causes data loss and silent failures. Browsers still only warn because crashing the page would break user experience. Both should be fixed.
Does async/await automatically handle Promise rejections?
No. async/await is syntactic sugar over Promises. `await asyncFn()` converts a rejected Promise into a thrown exception inside the async function — but only if you have a try-catch to catch it. Without try-catch, the exception propagates and becomes an unhandled rejection on the calling Promise.
What is the difference between .catch() and a second argument to .then()?
.then(onFulfilled, onRejected) handles rejections from the previous Promise only. .catch(handler) is equivalent to .then(undefined, handler) and handles rejections from all previous steps. A .catch() at the end of a chain covers the entire chain; a rejection handler in .then() only covers the step before it.