async/await looks like magic — synchronous-looking code that doesn't block. Beneath the syntax sugar, it's generators combined with a Promise-driving function. This article desugars async/await step by step.
Prerequisites: Async #3 — Promises from Scratch, Async #6 — Generators & Iterators
1. What async/await Actually Does
An async function always returns a Promise — whatever you return becomes the resolved value. Inside, await pauses the function's execution until a promise settles, then unpacks its resolved value:
Loading editor...
Key points before we dig into the internals:
awaitonly blocks THIS function, not the whole thread. Other code keeps running.awaitworks on any thenable, not just Promise — it calls.then()under the hood.await 42(non-promise) just wraps it:Promise.resolve(42).- Errors can be caught with
try/catcharoundawait, just like synchronous code.
2. Generators Can Pause and Resume
A generator can yield values and later resume from where it left off. This "pause-and-resume" is the exact mechanism await needs:
Loading editor...
Every .next(arg) passes arg back to where yield left off. This is how we'll pass resolved promise values back into the async function.
3. Desugaring async/await to a Generator + Runner
The pattern: yield every promise, and a runner function calls .next() with each resolved value:
Loading editor...
4. Error Handling in the Desugared Version
Errors propagate naturally — the runner passes rejections to gen.throw(), which becomes a thrown exception at the yield point:
Loading editor...
5. Sequential vs Concurrent await
await is sequential by default — each one waits for the previous. That's often wasteful when operations are independent:
Loading editor...
6. The async to Generator Transformation
In summary, this:
Roughly becomes:
Where asyncRunner is our generator-driving function that chains .then() calls on every yielded promise.
Loading editor...
Key Takeaways
async/awaitis syntax sugar over generators + a Promise-based runner.yieldpauses;.next(value)resumes with the value — this is how resolved promise values get passed back.gen.throw(error)propagates a rejected promise into the generator as an exception → enablestry/catcharoundawait.- The runner wraps everything in a Promise — the generator's
returnvalue becomes the resolved value. - Concurrent vs sequential:
awaitonly blocks the async function, not the thread. Start promises first, then await, for concurrency.
Next: Async #7 — Async Iteration — for await...of, async generators, and streaming data patterns.
