UnhandledPromiseRejectionWarning / Uncaught (in promise) (tested)

A rejected Promise with no .catch() or surrounding try/catch is an unhandled promise rejection. Old Node.js printed a warning and kept running. We tested current Node and current headless Chromium side by side to see what actually happens now.

1. The two different behaviours, measured

We ran the same bug — Promise.reject(new Error(...)) with nothing attached to it — in Node.js 24.21.0 and in headless Chromium 153 (via Playwright). The outcomes are not the same.

EnvironmentWhat we sawProcess/page survives?
Node.js 24.21.0Prints the error and stack straight to stderr, exits with code 1No — process exits
Headless Chromium 153unhandledrejection event fires; console shows "Uncaught (in promise) Error: ..."Yes — page keeps running

2. Node.js: it crashes, and the old warning text is gone

We ran this file with nothing else in it:

Promise.reject(new Error("boom - no handler"));
console.log("this line still runs synchronously");

Exact output on Node.js 24.21.0:

this line still runs synchronously
file:///tmp/unhandledrej-guide/node_crash.mjs:1
Promise.reject(new Error("boom - no handler"));
               ^

Error: boom - no handler
    at file:///tmp/unhandledrej-guide/node_crash.mjs:1:16
    at ModuleJob.run (node:internal/modules/esm/module_job:561:25)
    ...

Node.js v24.21.0

Exit code was 1. Note what is not there: there is no line starting with UnhandledPromiseRejectionWarning. That text was the behaviour in old Node (≤ 14, before the default flipped). Current Node prints the error like an uncaught exception and kills the process outright. If you still see "UnhandledPromiseRejectionWarning" in your logs, you are on an old Node version or reading an old stack trace saved somewhere — it is not what current Node prints.

3. The common real cause: a floating promise

You rarely write Promise.reject() directly. The usual way to hit this is forgetting to return or await a promise, or chaining .then() without a paired .catch():

async function fetchThing() {
  throw new Error("fetch failed inside async fn");
}

function run() {
  // BUG: no return, no await, no .catch()
  fetchThing().then(r => console.log("got", r));
}

run();

We ran this on Node.js 24.21.0. run() returns immediately (synchronous code finishes fine), but when the rejected promise from fetchThing() is checked on the next microtask turn with no .catch() attached, Node prints the same style of error and stack pointing at the throw site, then exits with code 1. The fix is any one of: await fetchThing() inside an async function wrapped in try/catch, return fetchThing()... so the caller's chain handles it, or adding .catch(err => ...) to the chain itself.

4. Fix #1: a .catch() or try/catch

// Promise chain
fetchThing().then(r => console.log(r)).catch(err => console.error("failed:", err.message));

// async/await
async function run() {
  try {
    const r = await fetchThing();
    console.log(r);
  } catch (err) {
    console.error("failed:", err.message);
  }
}

This is the real fix: handle the rejection where it happens. The global handlers below are a safety net, not a substitute.

5. Fix #2 (Node): a global process.on('unhandledRejection') handler

We added this one line before the same Promise.reject() call:

process.on("unhandledRejection", (reason, promise) => {
  console.log("CAUGHT by unhandledRejection handler:", reason.message);
});
Promise.reject(new Error("boom - no handler"));

Measured result: Node printed CAUGHT by unhandledRejection handler: boom - no handler, the rest of the script kept running (a later setTimeout still fired), and the process exited with code 0. The crash is fully suppressed. Treat this as a last-resort logger/reporter (e.g. send to your error tracker), not a way to silently ignore bugs — per the Node.js docs, relying on it instead of fixing the rejection is explicitly discouraged.

6. Fix #2 (browser): window.addEventListener('unhandledrejection', ...)

In headless Chromium 153 we confirmed the rejection fires a real DOM event with the reason attached:

window.addEventListener("unhandledrejection", ev => {
  console.log("reason:", ev.reason);
});
Promise.reject(new Error("boom in browser - no handler"));

Measured: the unhandledrejection event fired with ev.reason set to the Error object, and Playwright's page also reported a pageerror with message "boom in browser - no handler" (this is what shows as Uncaught (in promise) Error: boom in browser - no handler in a real DevTools console). The page kept running fine afterward — a follow-up 1 + 1 evaluation on the same page still returned 2. Calling ev.preventDefault() inside the listener measurably suppressed that console error entirely in our test: with preventDefault() called, no pageerror fired at all.

7. Node vs browser, side by side

Node.js 24.21.0Chromium 153
Default behaviourCrashes, exit code 1Logs to console, keeps running
Event/hook nameprocess.on("unhandledRejection", (reason, promise) => ...)window.addEventListener("unhandledrejection", ev => ...)
Can you stop the default report?Yes — attaching the handler is enough to stop the crashYes — call ev.preventDefault() inside the listener
Should you rely on this as the fix?No — add .catch()/try-catch at the sourceNo — same

8. Checklist

If your actual crash is a different runtime error, see PlainHeaders for inspecting what your server sends when a request handler throws.

Frequently asked questions

Does Node.js always crash on an unhandled promise rejection?

On current Node (we tested 24.21.0), yes by default: it prints the error and stack to stderr and exits with code 1. Older Node (8-14) only printed a DeprecationWarning/UnhandledPromiseRejectionWarning and kept running. If you need the old behaviour you can run with --unhandled-rejections=warn, per the Node docs (not measured here).

Does the browser crash the page on an unhandled rejection?

No. In our headless Chromium 153 test, a rejected promise with no handler fired the unhandledrejection event and logged an 'Uncaught (in promise)' error to the console, but the page kept running normally afterward.

What's the actual fix, not just suppressing the message?

Attach a .catch() to the promise chain, or wrap an awaited call in try/catch. We confirmed both stop the error entirely at the source, which is better than a global handler that only logs it after the fact.

What does process.on('unhandledRejection') receive?

In our test it was called with (reason, promise), where reason was the Error object (reason.message gave the message text). Attaching this handler measurably stopped Node from crashing and exiting.

What is the most common real-world cause?

A floating promise: calling an async function with .then() but no .catch(), or forgetting to await/return a promise inside another async function. We reproduced this exact pattern and got the identical crash and exit code 1 as a bare Promise.reject().

Written with AI assistance. Measured on 2026-10-02 with Node.js 24.21.0 and headless Chromium 153 via Playwright 1.63. Ran a rejected promise with no handler, a floating .then() with no .catch(), and both global-handler fixes, and recorded the exact exit codes and printed text. PlainHeaders is our extension; Node.js and Chromium are free.