Recommended Free Tools
If a promise inside a .then() seems to run out of order or escape your .catch(), the usual cause is that the inner asynchronous operation was started but not returned. Returning it connects the work to the chain: the next promise adopts its eventual fulfillment or rejection. For ordinary sequences, use a flat promise chain or async/await; reserve promise combinators for independent tasks.
What does “a promise inside a promise” mean?
The phrase can describe two related behaviors. A promise may be resolved with another promise, or a .then() handler may return a promise. In both cases, JavaScript follows the inner promise’s eventual state rather than treating that promise as an ordinary fulfillment value.
As an Amazon Associate I earn from qualifying purchases.
Promise resolution adopts the inner promise
When an outer promise is resolved with an inner promise, the outer promise becomes locked to follow it. It can be considered resolved while still pending: it does not fulfill until the inner promise fulfills, and it rejects if the inner one rejects. This behavior is called promise adoption or assimilation. See MDN’s Promise reference.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →A returned promise becomes part of the chain
Every call to .then() creates a new promise. If its handler returns a promise or thenable, that new chain promise adopts the returned value’s eventual state. A rejection therefore continues down the chain unless a handler handles it.
#1 Best Overall
By contrast, starting asynchronous work without returning it detaches that work from the chain. The next handler can run before it finishes, and the outer chain does not automatically wait for or catch its rejection.
// Detached: the chain does not wait for saveRecord or catch its rejection.
getRecord(id).then((record) => {
saveRecord(record); // Missing return
}).then(() => showSaved());
// Connected: the next step waits for saveRecord; failures reach catch.
getRecord(id)
.then((record) => saveRecord(record))
.then(() => showSaved())
.catch(reportFailure);
Return the asynchronous operation from the handler, or return an async function call. That makes the chain’s result represent the whole sequence.
Do not wrap an existing promise without a reason
return new Promise((resolve) => resolve(otherPromise)) does not preserve a useful extra layer: the outer promise adopts the inner one. Wrapping a function that already returns a promise in a new Promise is usually the Promise constructor antipattern. Use the constructor when you genuinely need to bridge a callback-based API or another boundary that does not already provide a promise.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsRank #2
Why do promise callbacks run “out of order”?
The executor passed to new Promise() runs during construction. Handlers registered with .then(), however, do not run inline. Once the relevant promise settles, its reactions are queued as jobs. A handler added to an already-settled promise is queued too, not called synchronously. Chained handlers then run in dependency order.
Promise.resolve()
.then(() => console.log("first"))
.then(() => console.log("second"));
console.log("sync");
// sync
// first
// second
The synchronous log appears first because the current JavaScript work finishes before the queued promise reactions run. An await also defers the async function’s continuation, even when the awaited value is already fulfilled. It suspends only that function’s dependent code; the rest of the program continues. MDN notes that “the await expression never blocks the main thread”.
Promises coordinate asynchronous processes; they are not lazy computations and do not create parallel CPU execution. I/O operations may overlap, but ordinary JavaScript main-thread code still runs one task at a time.
Common nested-promise bugs and their fixes
1. Starting work but forgetting to return it
Symptom: A later handler runs before a request or save finishes, or its rejection bypasses the chain’s .catch().
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Fix: Return the operation: return fetch(url), return save(record), or return loadData(). A concise arrow handler such as .then(record => saveRecord(record)) returns its expression automatically.
2. Using a promise as if it were its value
Symptom: A log shows a pending Promise, or code reads a property before the operation fulfills.
Rank #4
Fix: In an async function, use const value = await getValue(); in a chain, use .then(value => ...). await gives you the fulfillment value and throws at that expression if the awaited promise rejects.
3. Catching an error and unintentionally recovering
A .catch() handler is a recovery handler. If it logs an error and returns normally, the promise produced by that handler fulfills with the returned value—often undefined—so later chain steps continue.
loadData()
.catch((error) => {
console.error(error);
throw error; // Preserve the rejection for a later handler.
})
.then(renderData)
.catch(showFailure);
Return an explicit fallback only when continuing is intended. Otherwise rethrow the error or return a rejected promise. A local catch can also be useful for optional work: it can supply a fallback for that work while leaving failures in the critical path to an outer catch. MDN’s guide to using promises recommends keeping ordinary promise chains flat and using nesting only when it deliberately limits a recovery boundary.
Best Value
4. Expecting try/catch to catch an un-awaited rejection
A synchronous try/catch around a call to an async function does not catch a rejection that happens later if the returned promise is not awaited:
async function load() {
try {
const data = await getData();
return data;
} catch (error) {
reportFailure(error);
throw error;
}
}
Alternatively, attach .catch() to the returned promise. Also, if a function can throw synchronously while it is being called, that throw happens before a chained .catch() can be attached; put the invocation inside a try/catch when that possibility matters.
5. Serializing independent operations by accident
Awaiting each independent request inside a loop starts the next only after the previous one finishes. Start the operations independently and select the combinator that matches the outcome you need:
| Need | Pattern | Outcome |
|---|---|---|
| Every independent operation must fulfill | Promise.all() |
Fulfills with the values when all fulfill; rejects if an input rejects. |
| Inspect every success and failure | Promise.allSettled() |
Waits for all outcomes instead of short-circuiting on rejection. |
| Use the first successful result | Promise.any() |
Fulfills on the first fulfillment; rejects if all inputs reject. |
| Use whichever operation settles first | Promise.race() |
Adopts the first settlement, whether fulfillment or rejection. |
These methods do not cancel inputs that lose the race or are no longer needed. If the underlying API supports cancellation, use its cancellation mechanism, such as an AbortSignal.
6. Assuming a timeout race stops the slower operation
Promise.race() settles the promise returned by the race; it does not stop the other input. If a timeout wins, a network request or other underlying operation may keep running. Pair the operation with a cancellation mechanism supported by that API when stopping it matters. Promises do not have a general cancellation protocol.
Should you use a chain, async/await, or a combinator?
| Situation | Clear choice | Why |
|---|---|---|
| Step B needs the result of step A | Flat .then() chain or sequential await |
Makes the dependency explicit and lets the returned chain represent the sequence. |
| Several operations are independent and all are required | Promise.all() |
Collects their values on success and rejects if an input rejects. |
| Every independent outcome must be reported | Promise.allSettled() |
Waits for all outcomes. |
| Any one successful result is enough | Promise.any() |
Settles successfully on the first fulfillment. |
| The first settlement should decide | Promise.race() |
Uses the first fulfillment or rejection, without canceling other inputs. |
| Optional work may fail without aborting the main task | Local .catch() or inner try/catch |
Defines a deliberate, narrow recovery boundary. |
For deeper reference, MDN’s JavaScript promises guide covers chaining and nesting, while its await reference explains what is suspended and how rejection is handled.
Quick Recap
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.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.




