This post explains a quiz originally shared as a LinkedIn poll.
🔹 The Question
async function first() {
console.log(1);
await second();
console.log(2);
}
async function second() {
console.log(3);
await Promise.resolve();
console.log(4);
}
console.log(5);
first();
console.log(6);
Hint: Think about when await actually pauses execution and what runs synchronously before the first suspension point in each function.
🔹 Solution
Correct Answer: A) 5, 1, 3, 6, 4, 2
The output is: 5, 1, 3, 6, 4, 2
🧠 How this works
This quiz tests a fundamental misunderstanding about async/await: an async function runs synchronously up until its first await expression. The await keyword is the suspension point — everything before it executes immediately on the call stack, just like regular synchronous code.
When await encounters a resolved or pending Promise, it doesn't block the thread. Instead, it schedules the rest of the function (the continuation) as a microtask and returns control to the caller. The caller then continues executing synchronously.
The critical insight is that await on an already-resolved Promise (like Promise.resolve()) still causes a suspension. It doesn't short-circuit. The continuation is placed on the microtask queue and executes after the current synchronous call stack completes.
With nested async functions, this creates a chain: second() suspends first, and when it resumes and completes, only then does first() resume — each resumption happening in a separate microtask.
🔍 Line-by-line explanation
console.log(5)— synchronous, runs immediately. Output: 5first()is called — enters the async function synchronouslyInside
first:console.log(1)— still synchronous (before the firstawait). Output: 1Inside
first:await second()— callssecond()synchronously before awaiting its resultInside
second:console.log(3)— still synchronous (before second's firstawait). Output: 3-
Inside
second:await Promise.resolve()— this is the first real suspension point:-
Promise.resolve()creates an already-resolved Promise - But
awaitstill suspendssecond()— it schedules the continuation (console.log(4)) as a microtask -
second()returns a pending Promise tofirst()
-
Back in
first:await second()— the Promise fromsecond()is still pending (second hasn't finished), sofirst()is also suspended. Control returns to the top-level caller.console.log(6)— synchronous code in the top-level script. Output: 6Synchronous call stack is empty. Microtask queue processes:
Microtask 1: Resume
second()—console.log(4)runs. Output: 4.second()completes, and its Promise resolves. This resolution schedules another microtask to resumefirst().Microtask 2: Resume
first()—console.log(2)runs. Output: 2
The misleading part: Many developers expect await on an already-resolved Promise to continue synchronously (like an immediate return), producing 5, 1, 3, 4, 2, 6. But the spec requires that await always yields at least once, even for resolved Promises. This is by design — it ensures consistent, predictable scheduling regardless of whether a Promise was pre-resolved or will resolve later.
🔹 Key Takeaways
- An
asyncfunction executes synchronously up to its firstawait— it's not fully asynchronous from the start -
awaiton an already-resolved Promise still suspends execution — it doesn't continue synchronously - Each
awaitresumption happens as a separate microtask, which runs after the current synchronous call stack clears - Nested async functions create a chain of microtasks — each inner resumption must complete before the outer one can resume
- Never assume an async function has completed just because you called it — always
awaitor.then()if you depend on its side effects - The microtask queue (Promises,
queueMicrotask) always drains before the macrotask queue (setTimeout,setInterval, I/O)
Top comments (0)