Glossary / Unhandled promise rejections
Unhandled promise rejections
An unhandled promise rejection occurs when a promise rejects and no rejection handler is attached within the host's opportunity to observe it. It often indicates a lost async error path: a missing await, an ignored returned promise, a fire-and-forget operation without reporting, or a rejected promise created inside a callback that the caller does not track.
How errors become unhandled
A rejection handler attached with .catch() or the second argument to .then() marks the rejection as handled and propagates according to the handler's result. await observes rejection by throwing into the surrounding async function, where it must be caught or returned as a rejection. Attaching a handler in a later turn may cause the host to report the rejection first and later report that it became handled.
async function save() {
await persist(); // rejection propagates to save()'s caller
}
save(); // if caller ignores this promise, the failure may become unhandled
In browsers, unhandledrejection is a global event, subject to cross-origin restrictions and host policy. In Node.js, unhandledRejection is a process event, and default behavior has changed across Node releases and can be configured. Do not rely on a particular warning-versus-termination default; pin and document runtime policy. A global handler is a last-resort observability hook, not a substitute for local recovery.
Correct handling
At an ownership boundary, decide whether to recover, retry, convert to a domain result, or propagate. For intentional fire-and-forget work, attach a handler that logs or reports with enough operation context. Avoid empty .catch(() => {}), which hides failures. In parallel work, Promise.all propagates the first rejection but does not cancel siblings; use explicit cancellation when needed. Use allSettled when every result must be inspected.
Debugging guidance
Enable strict rejection handling in development/CI, capture stack traces and correlation IDs, and inspect ignored promise returns in event handlers, timers, and callbacks. Ensure logging itself does not throw or leak secrets. A rejection handler that throws creates a new rejected promise, which also needs an owner.
Interview questions
- Does calling an async function handle its errors? No; it returns a promise that rejects if the function throws or awaits a rejection.
- Why is a global handler insufficient? It may observe failure too late to recover, lacks operation ownership, and can mask broken local control flow.
- What if a catch handler throws? The promise returned by
catchrejects with that new error. - Does
Promise.allcancel other operations after one rejects? No; sibling work continues unless explicitly canceled. - How should intentional detached work be handled? Attach an explicit rejection reporting or recovery handler at the point ownership is detached.