Async/Await Under the Hood
How async/await compiles to state machines and Promise chains β the syntactic sugar that transformed JavaScript async programming.
async/await is syntactic sugar over Promises. An 'async' function returns a Promise. 'await' suspends execution at that point, lets the event loop continue, and resumes when the awaited Promise settles. Under the hood, the engine compiles this into a state machine.
An async function ALWAYS returns a Promise. Even if you return a plain value (return 42), it's wrapped in Promise.resolve(42). If your function throws, the returned Promise is rejected.
When the engine hits 'await expr', it: 1) Evaluates expr (creates/gets a Promise). 2) Suspends the async function's execution. 3) Returns control to the caller (event loop continues). 4) When the Promise settles, resumes the function from that point.
The engine compiles async/await into a state machine (generator-like). Each 'await' becomes a state boundary. The function's local variables are captured so they survive across suspensions. This is why the function can resume exactly where it left off.
await unwraps the Promise. If the Promise rejects, 'await' re-throws the rejection as a regular exception. This is why try/catch works with async/await β rejected Promises become caught exceptions.
Key Concepts
Syntactic sugar that makes a function return a Promise and enables the 'await' keyword inside it.
Pauses async function execution until the awaited Promise settles. Returns the fulfilled value or throws the rejection reason.
ES2022 feature allowing 'await' at module top level (not inside a function). Only works in ES modules, not CommonJS.
Iterates over async iterables (e.g., readable streams). Each iteration awaits the next value.
1// async function = returns Promise automatically2async function getUser(id) {3 try {4 const resp = await fetch(`/api/users/${id}`);5 if (!resp.ok) throw new Error(`HTTP ${resp.status}`);6 const user = await resp.json();7 return user; // resolves the Promise8 } catch (err) {9 console.error("Failed:", err);10 throw err; // rejects the Promise11 }12}1314// Sequential vs Parallel:15// β Sequential (slow β waits for each):16const users = await getUser(1);17const posts = await getPosts(1);1819// β Parallel (fast β runs simultaneously):20const [users, posts] = await Promise.all([21 getUser(1),22 getPosts(1),23]);
async/await makes asynchronous code read like synchronous code, dramatically improving readability. But understanding the underlying mechanics prevents common bugs like sequential awaits, stale closures, and unhandled rejections.
Common Pitfalls
1Accidental Sequential Awaits
Your API handler fetches user data, permissions, and preferences. It takes 3 seconds because each await runs sequentially, even though the calls are independent.
Writing 'const user = await getUser(); const perms = await getPerms();' means getPerms() doesn't start until getUser() finishes. Three 1-second calls take 3 seconds total.
Start all Promises first, then await them: 'const [user, perms, prefs] = await Promise.all([getUser(), getPerms(), getPrefs()])'. All three run in parallel β total time is ~1 second.
Takeaway: Sequential awaits are the #1 async performance mistake. Always ask: 'Does this await depend on the result of the previous one?' If not, use Promise.all() for parallelism.
2Stale Closures in React useEffect
Your useEffect calls an async function that reads a state variable. The effect shows stale data even though the state has been updated.
The async function closes over the state value at the time the effect ran. If state updates while the async operation is in flight, the resumed function still sees the OLD value from its closure.
Use the cleanup function to set an 'isCancelled' flag. When the async function resumes, check the flag before updating state. Or use useRef to always read the latest value.
Takeaway: async/await in React creates closures around state snapshots. Combined with Strict Mode's double-invocation, this causes subtle bugs. Understanding both closures and async/await together is essential for React development.