The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →A rejected Promise stays rejected through each chained .then() that has no rejection handler. A .catch() can recover that chain by returning normally, or keep it rejected by throwing or returning a rejected Promise. Each call creates a new Promise, so the important question is what state each link returns—not whether a catch changes the original Promise.
How rejection moves from one Promise to the next
Every call to .then() returns a new, derived Promise. It does not change the Promise on which .then() was called. When the source Promise is rejected, JavaScript looks for a callable rejection handler at that link:
- If there is no callable rejection handler, the derived Promise rejects with the same reason.
- If a rejection handler runs and returns a non-thenable value, the derived Promise fulfills with that value. An implicit return of
undefinedcounts as a normal return. - If the handler throws, the derived Promise rejects with the thrown value.
- If it returns a Promise or another thenable, the derived Promise adopts that thenable’s eventual state and result.
These are the Promise rules described in MDN’s Promise.prototype.then() reference and Promise reference.
Trace a rejection one link at a time
Promise.reject(new Error("original"))
.then(value => value) // no rejection handler: remains rejected
.then(value => value) // still rejected; fulfillment callbacks are skipped
.catch(error => {
console.error(error); // handles this link
return "fallback"; // this catch's Promise fulfills
})
.then(value => console.log(value)); // receives "fallback"
The first two .then() calls have no rejection handlers, so each returned Promise stays rejected with the original reason. .catch(handler) is equivalent in behavior to .then(undefined, handler); it handles the rejection at its own link. Because the handler returns "fallback", the Promise returned by .catch() fulfills, and the next fulfillment callback receives that value. The original rejected Promise remains rejected. MDN documents catch behavior in its Promise.prototype.catch() reference.
Recommended Free Tools
#1 Best Overall
Why code after a catch can run
A catch is not only an error logger: its return value determines the state of the Promise it returns. Returning a fallback value—including implicitly returning undefined—handles the rejection and makes the next link a fulfillment path.
Promise.reject(new Error("failure"))
.catch(error => {
console.error(error);
// implicit return undefined: recovered
})
.then(() => console.log("This runs"));
To keep the chain rejected after logging or adding context, throw from the handler or return a rejected Promise. A later catch can then handle that new rejection:
Rank #2
Promise.reject(new Error("failure"))
.catch(error => {
console.error(error);
throw error;
})
.catch(error => console.log("Still rejected:", error));
Return nested asynchronous work so its rejection joins the chain
When a handler starts another asynchronous operation, return its Promise if subsequent chain steps depend on its result or failure:
fetchData()
.then(data => {
return saveData(data);
})
.catch(handleError);
Returning saveData(data) makes the Promise from .then() adopt that operation’s eventual outcome. If saveData rejects, the downstream catch can handle the rejection. By contrast, if the callback calls saveData(data) without returning it, the callback itself completes normally—typically with undefined—and the outer chain does not wait for that inner Promise. Its rejection is not carried by that chain, and later steps may run before the save finishes. MDN’s Using promises guide explains this floating-Promise pitfall.
A catch handles one branch, not every branch
Calling .then() or .catch() more than once on the same Promise creates separate derived Promises. Handling one branch does not recover another:
const source = Promise.reject(new Error("failure"));
const recoveredBranch = source.catch(() => "fallback");
const stillRejectedBranch = source.then(value => value);
recoveredBranch fulfills with "fallback". stillRejectedBranch has no rejection handler at its link, so it remains rejected. A catch attached to recoveredBranch would not handle stillRejectedBranch; attach handling to the branch whose rejection you need to address. An unhandled branch may also be reported by the runtime.
Rank #4
A reliable method for tracing a nested chain
- Name each Promise. Call the starting Promise
p0, then label each returned Promisep1,p2, and so on. - Check the source state at each link. A fulfillment handler runs for fulfillment; a rejection handler runs for rejection. If the relevant handler is absent or not callable, the derived Promise preserves the source state and value or reason.
- Record the handler’s outcome. A normal return of a non-thenable fulfills the derived Promise; a returned Promise or thenable is adopted; a thrown value rejects it.
- Follow the returned Promise. A later catch handles a rejection only if it is attached to that Promise’s chain or branch.
- Check nested work. If a handler starts asynchronous work, verify that it returns the inner Promise when the outer chain must wait for or handle its result.
Promise semantics versus unhandled-rejection reports
Promise propagation rules determine the state of each derived Promise. Runtime notifications are a separate reporting layer. MDN describes the browser unhandledrejection event for a rejected Promise without a handler available at the relevant check, and rejectionhandled when a handler is attached later. In Node.js, the corresponding process-level event is named unhandledRejection. Event names and runtime behavior differ by environment and version; consult the documentation for the specific browser or Node.js version you target. These notifications do not change how a rejection propagates through a chain. Logging an event is not a substitute for handling the relevant branch in application code.
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.




