A Promise’s executor runs immediately, but its .then() handlers run later. In browsers, those handlers are queued as microtasks, which run after the current JavaScript finishes and before the event loop takes another task. That distinction explains why Promises can defer reactions without moving ordinary JavaScript work onto another thread.
What a Promise represents—and what it does not
A Promise is a JavaScript object representing an outcome that may become fulfilled or rejected. It can track the result of work that completes later, such as a network request, but the Promise itself does not make that work asynchronous or run it on a separate thread.
As an Amazon Associate I earn from qualifying purchases.
When code calls new Promise(executor), JavaScript invokes executor immediately. The executor may start an operation that completes later. Calling its resolve or reject function settles the Promise, or locks it to follow another Promise or thenable. Reactions registered with .then() are delivered later, not as part of the immediate executor call.
Why a Promise reaction runs after synchronous code
Consider this browser example:
console.log("sync start");
Promise.resolve().then(() => console.log("promise reaction"));
setTimeout(() => console.log("timer task"), 0);
console.log("sync end");
The output is:
sync start
sync end
promise reaction
timer task
The first two logs execute in the current JavaScript job. The Promise reaction is queued as a microtask, while the timer callback is a task. In the browser event-loop model, after the current job completes, microtasks are processed before the event loop selects another task. The timer’s 0 does not mean an exact delay or guarantee a fixed callback time.
#1 Best Overall
As MDN Web Docs puts it: “To avoid surprises, functions passed to then() will never be called synchronously, even with an already-resolved promise:” MDN’s Promise guide explains this behavior and the interaction between Promise reactions and tasks.
Microtasks defer work; they do not create another thread
A microtask runs after the current job, but it still runs JavaScript on the same agent. Each job runs to completion. A long synchronous calculation—or a long chain of microtasks—can therefore delay input handling, timers, and other callbacks. Promises are useful for organizing asynchronous results, but they are not a way to make CPU-heavy JavaScript run in parallel.
Rank #2
The Promise and job semantics belong to JavaScript, while the event loop and its task and microtask scheduling are provided by the host environment. The browser ordering above describes the ordinary web-platform case; Node.js has additional scheduling details, so browser ordering should not be generalized to every host. See MDN’s JavaScript execution model for the distinction.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsWhat await pauses
await suspends the rest of the async function containing it until the awaited value settles. It does not block the caller or freeze the whole program. The caller and unrelated work can continue while that function is waiting. When the Promise fulfills, the await expression evaluates to its fulfillment value; if it rejects, the rejection is thrown at the await point.
async function loadLabel() {
try {
const label = await getLabel();
console.log(label);
} catch (error) {
console.error("Could not load label", error);
}
}
loadLabel();
console.log("caller continues");
The caller can log before loadLabel resumes. Even awaiting an already-fulfilled Promise defers the async function’s continuation rather than blocking the main thread. MDN’s documentation for await describes how fulfillment values and rejections are handled.
How rejection moves through Promise code
A rejection is the failure path of a Promise chain. Use .catch() to handle a rejection in a chain, or wrap an await in try/catch to handle it as a thrown value. If a rejection is not handled, it remains an unhandled rejection; the host may report it.
Rank #4
getLabel()
.then(label => label.toUpperCase())
.catch(error => {
console.error("Could not load label", error);
});
Handling a rejection also affects the chain’s outcome: a handler that returns a normal value allows the next Promise in the chain to fulfill with that value, while a handler that throws or returns a rejected Promise keeps the failure path going. For the core Promise behavior, see MDN’s Promise reference.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchChoose a Promise combinator by its completion rule
Promise combinators coordinate outcomes; they are not parallel-execution commands. Independent operations can be pending at the same time, but a JavaScript agent executes one job at a time.
Best Value
| Method | When the aggregate settles | Rejection behavior |
|---|---|---|
Promise.all() |
After every input fulfills | Rejects when an input rejects |
Promise.allSettled() |
After every input settles | Fulfills with each input’s outcome, whether fulfilled or rejected |
Promise.any() |
When the first input fulfills | Rejects if all inputs reject |
Promise.race() |
When the first input settles | Adopts that first outcome, fulfillment or rejection |
Choose all when every result is needed and any failure should fail the aggregate; choose allSettled when every outcome matters, including failures. Use any when one successful result is enough, or race when the first outcome of either kind should determine the aggregate.
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.




