Case 6/15

Prefer reading? The full explanation

How await Runs

async / await · Browser

Before this

How Promises Run — microtasks versus tasks.

After this you can explain

Exactly where await suspends a function and when its continuation resumes.

async function main() {
  console.log('1: Main Start');
  await showMessage();
  console.log('2: Main End');
}

async function showMessage() {
  console.log('3: Show Message');
}

console.log('4: Script Start');
main();
console.log('5: Script End');

Output: 4: Script Start → 1: Main Start → 3: Show Message → 5: Script End → 2: Main End

Why does 5 print before 2?

The output is 4 → 1 → 3 → 5 → 2. The two common wrong answers map to two different misconceptions. Expecting 2: Main End before 5: Script End means treating await as something that blocks the whole thread. Expecting 5 before 1 means assuming an async function defers its body the way setTimeout defers a callback. Neither matches what the engine does.

Async functions run synchronously until the first await

Calling main() executes its body immediately, on the current call stack, the same as any other function call. Execution continues until the first await, which is why 1: Main Start and 3: Show Message print before anything is deferred. The async keyword changes the return type to a promise; it does not postpone the body.

showMessage() contains no await at all, so it runs straight through and returns an already-resolved promise.

await suspends the function and queues a microtask

At await showMessage(), the engine suspends main(). Its frame leaves the call stack, and the code after the await (here, console.log("2: Main End")) is packaged into a continuation that goes into the microtask queue.

The thread does not wait around. Control returns to the global script, which prints 5: Script End and finishes. With the stack empty, the event loop drains the microtask queue, main() resumes, and 2: Main End prints last.

The continuation has no special priority. It queues in FIFO order next to every other microtask, which the experiment above demonstrates against an explicit Promise.then. One detail worth knowing when you read older material: since V8 7.2 (Chrome 73, Node 12), awaiting a native promise resumes in a single microtask tick, while earlier engines took three, so pre-2019 articles report different orderings for more elaborate snippets.

Reading about it is one thing. Watching it run is another.

↑ Run it step by step

In the wild

Where this rule bites real code

The click handler that double-submits

Symptom
Users double-click "Pay" and two orders go through, despite a submitting flag that "should" prevent it.
Why
The handler is async and sets the flag AFTER an early await. Both clicks run their synchronous prefix — including the flag check — before either continuation resumes.
Fix
Set the guard synchronously, before the first await, and disable the button in the same tick. Everything before the first await is your only atomic window.

The sequential loop that should have been parallel

Symptom
Loading 20 thumbnails takes 20× the single-item latency; the network tab shows requests running one at a time.
Why
An await inside a for-loop suspends each iteration until the previous request settles — that is serialisation by syntax.
Fix
Start all the promises first, then await together: await Promise.all(items.map(load)). Await in a loop only when order genuinely matters.

Environment

Browser

CI verified

Node.js v22.22.3

Last reviewed

29 July 2026

The animation on this page is a teaching model, not a diagram of browser internals. Real engines use several task queues, optimise aggressively, and do plenty of work these panels do not show. What is guaranteed is the observable ordering: the stated output is produced by actually executing the code in CI, on the runtime named above. Browser-specific behaviour follows the HTML Standard and is not executed in a real browser by CI. How this is verified →