Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →To keep a nested promise’s failure visible, return or await that promise so it joins the caller’s chain. If you catch an error only to log it or clean up, throw it again; a .catch() callback that returns normally turns the result of .catch() into a fulfilled promise.
Why can .catch() make a promise resolve?
.catch(onRejected) returns a new promise. If onRejected returns normally, that new promise fulfills with the returned value—even when the promise before .catch() was rejected. A log-only handler usually returns undefined, so downstream code continues as fulfilled with undefined.
As an Amazon Associate I earn from qualifying purchases.
operation()
.catch((error) => {
console.error(error);
// Returns normally: the next promise fulfills with undefined.
})
.then(() => continueWork());
This is not an automatic bug: a catch that returns a usable fallback is a recovery. It is a problem when the intent was only to record the error while leaving the operation failed.
Choose whether to recover or preserve the failure
Recover locally with a meaningful fallback
When the current layer can genuinely recover, return a value that downstream code can use. Later chain steps receive that value as success, rather than the original rejection. This behavior follows from the Promise catch() reference on MDN.
#1 Best Overall
return loadSettings()
.catch((error) => {
logError(error);
return defaultSettings; // Deliberate recovery; downstream receives a value.
});
Log, clean up, or add context while keeping the rejection
If a caller still needs to know the operation failed, throw after the local action. The promise returned by the catch then rejects, allowing the next error boundary to decide what to do. MDN describes the rule this way: “Therefore, if an error must be handled immediately, but we want to maintain the error state down the chain, we must throw an error of some type in the rejection handler.” See the MDN Promise reference.
return loadSettings()
.catch((error) => {
logError(error);
throw error;
});
When adding context, preserve the original error as the cause if the runtime and codebase support that pattern:
Rank #2
return loadSettings()
.catch((error) => {
throw new Error("Could not load settings", { cause: error });
});
Return nested promises so the caller can observe them
A promise returned from a .then() callback is joined to the promise produced by that .then(). Its eventual fulfillment or rejection therefore travels through the resulting chain. MDN’s guide to using promises recommends keeping simple chains flat; nesting can also change which catch handles a failure.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsDetached inner work
function loadProfile(id) {
return fetchProfile(id)
.then((profile) => enrichProfile(profile))
.catch((error) => {
logError(error);
throw error; // The caller still sees a rejection.
});
}
// The inner promise is not returned, so this chain does not wait for it.
outer().then(() => {
inner();
});
Joined inner work
outer().then(() => {
return inner();
});
Returning the inner promise makes its result part of the outer chain. If it rejects, a catch on that resulting chain can handle it. Without the return, the outer chain can fulfill before the inner work finishes, and its catch is not automatically attached to the separate promise.
Use await inside the try that should catch the failure
In an async function, await turns a promise rejection into a thrown reason at that point in the function. Put the await inside the try whose catch should own the failure:
async function loadProfileForPage(id) {
try {
return await loadProfile(id);
} catch (error) {
showProfileError(error);
throw error; // Keep it failed if this layer cannot recover.
}
}
The await makes the local catch’s scope explicit. If the function does no work in the try after calling loadProfile and simply returns its promise, it can omit await and write return loadProfile(id); the returned promise still adopts that promise’s outcome. By contrast, try { doAsyncWork(); } catch (error) { ... } catches a synchronous throw made while invoking the function, not a later rejection from its unawaited promise. The MDN await reference explains how rejection is raised at the await expression.
Rank #4
Check the common traps
- Log-only catch: returning normally fulfills the promise returned by
.catch(). Throw again if propagation is required. - Fallback catch: returning a fallback intentionally recovers; later steps receive that fallback as success.
- Inner promise not returned: return it, or await it in an appropriate async function, so the caller’s chain can observe its outcome.
- Unawaited call inside
try: the try block does not wait for a later rejection. Await the operation or attach and route a rejection handler. - Async callback passed to an API: throwing inside the callback rejects the callback’s own promise. If the API does not use or await that promise, the outer operation is not necessarily joined to it; follow the API’s callback contract and route failure to the boundary that owns it.
- Catch on a different branch: a catch handles rejections that reach the promise it is attached to. Join separate work into the chain or handle that branch explicitly.
Handle errors at the application boundary, not as a host-level workaround
Browsers expose unhandledrejection when a rejected promise has no handler, and rejectionhandled if a handler is attached later. These events report promise handling status; they do not substitute for attaching a handler where the application knows how to recover or report the failure. See MDN’s promise guide.
Recommended Free Tools
Node.js v26.10.0 documents process events for unhandled and later-handled rejections. Its documented default --unhandled-rejections mode is throw, under which an unhandled rejection is raised as an uncaught exception. Runtime behavior depends on the Node.js version and flags; consult the Node.js process documentation for the version you deploy. Do not use process-level rejection or uncaught-exception handlers as a substitute for connecting the promise to its intended error owner.
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




