async/await Internals
Key Learning Objectives
Understand how async functions execute synchronously until reaching the first await expression.
Master how await transforms code into generator-like state machines and Promise microtasks.
Predict exact execution ordering when async/await interleaves with Promise.then() chains.
Recognize the microtask queue scheduling discipline across multiple await suspension cycles.
The Interview Problem
What is the exact sequence of outputs printed to the console when the following JavaScript code executes, and how do async/await suspension cycles interleave with Promise.then() microtasks?
1async function main() {2 console.log('1');3 await null;4 console.log('2');5 await Promise.resolve();6 console.log('3');7}89console.log('A');10main();11console.log('B');1213Promise.resolve()14 .then(() => {15 console.log('C');16 })17 .then(() => {18 console.log('D');19 });2021console.log('E');
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
A 1 B E 2 C 3 D
A B E 1 2 3 C D
A 1 B E C D 2 3
A 1 2 3 B E C D
V8 Engine Execution Trace
Step 1 of 11 (Line 9)Global synchronous execution begins. console.log('A') outputs 'A'.
Deep Technical Breakdown
The Mental Model: async/await as Generator + Promise State Machine
Under the hood in V8, async/await is syntactic sugar built on top of ECMAScript Generators and Promises:
- Synchronous Entry: Invoking an async function executes synchronously until the first
awaitexpression. It is NOT immediately queued onto the microtask queue. - The await Mechanism: When
await expris evaluated:expris converted to a Promise viaPromise.resolve(expr).- An internal reaction handler is attached to that Promise.
- The function's execution context is suspended, preserving all local variable bindings on the heap.
- Control yields immediately back to the caller frame.
- Resumption via Microtask: When the awaited promise fulfills, a microtask is pushed into the Microtask Queue to resume the async function context with the resolved value.
- Interleaving Discipline: Because microtasks execute in strict First-In, First-Out (FIFO) queue order, multiple async functions and
.then()chains interleave tick-by-tick. Eachawaitrepresents one full microtask turn.
Common Traps & Mistakes
Assuming calling an async function defers its first statement to a microtask. The first statement runs synchronously.
Sequential awaits where parallel `Promise.all` was intended: `const a = await getA(); const b = await getB();` creates unnecessary waterfall latency.
Forgetting that uncaught exceptions inside an async function reject the returned Promise rather than throwing synchronously to outer try/catch blocks.
FAANG Follow-Up Probes
Probe #1
How did the V8 engine optimize async/await in 2018 by eliminating redundant intermediate Promises (the 'Faster async functions and promises' change)?
Probe #2
How does Top-Level Await in ES2022 impact ES module graph loading and dependency execution order?
Probe #3
How do you implement an async task runner with concurrency limits (e.g., maximum 3 concurrent promises) using async/await?
