Under The Hood
frontend javascript ES2017+ (async/await, generators)

async/await: Generator De-Sugaring & Microtask Queue Steps

Last updated
Prerequisites:
Promises and the microtask queue
Understanding of generator functions and yield
  • async-await
  • generators
  • state-machine
  • microtask
  • promises
  • control-flow

Read at your depth

The practical view

async functions always return a Promise. Inside, await pauses the function until the awaited promise settles, then resumes with the resolved value (or throws on rejection, caught by try/catch). async function getData() { const res = await fetch(url); return res.json(); } is the modern replacement for .then chains. Await works on any thenable and even non-promises (they are wrapped). Async functions are built on generators + promises under the hood; each await is a suspension point. Use await in loops and conditionals naturally — no more promise chains or nesting. Remember: an unhandled rejection inside an async function without try/catch rejects the returned promise.

The same idea in other frameworks

react equivalent
React data fetching with async/await inside effects; Suspense integrates with promise-based loading.

Legacy vs modern

Promise chain vs async/await for sequential async steps

async/await reads like synchronous code and shares the same microtask machinery, avoiding nested .then callbacks and manual error handling.

before → after
Nested promise chains
fetch(url).then(r => r.json()).then(data => process(data)).catch(err => handle(err)); // ❌ OLD: linear but callback-heavy
async/await
async function load(url) {
  const r = await fetch(url); // ✅ NEW: suspension point, reads like sync
  return process(await r.json());
}

Interview gotchas

Context

Interviewers want the scheduling mechanics — that await resumption is a promise reaction enqueued as a microtask — which determines observable ordering in real apps.

The mechanical answer

An async function runs synchronously until its first await. The awaited promise is then processed via PerformPromiseThen, which registers a reaction (resume function). When the awaited promise settles, that reaction is enqueued on the microtask queue, not the macrotask queue. The event loop drains the microtask queue at checkpoints (after the current task and script), so resumption happens as soon as the current task completes — before any timers or I/O macrotasks. Because each await boundary enqueues a fresh microtask, an async function with multiple awaits interleaves with other promise-based work but never 'jumps the queue' ahead of timers. This is why awaiting in a loop yields control between iterations.

Trap

The naive answer is 'await returns control to the event loop as a new task'. Await resumes in the microtask queue, which has higher priority than tasks. Another trap: assuming await polls or blocks — it suspends the state machine and releases the thread; blocking happens only with Atomics.wait or synchronous busy loops. Candidates who name the promise reaction + microtask enqueue show they understand the mechanism.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.