Async #5 — async/await Under the Hood

Desugar async/await to generators + Promises. Understand paused execution, .next() driving the state machine, and why await blocks but doesn't freeze the thread.

12 min read
JavaScript
Async
Generators

TABLE OF CONTENTS
Async #5 — async/await Under the Hood

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:

  • await only blocks THIS function, not the whole thread. Other code keeps running.
  • await works 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/catch around await, 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/await is syntax sugar over generators + a Promise-based runner.
  • yield pauses; .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 → enables try/catch around await.
  • The runner wraps everything in a Promise — the generator's return value becomes the resolved value.
  • Concurrent vs sequential: await only blocks the async function, not the thread. Start promises first, then await, for concurrency.

Next: Async #7 — Async Iterationfor await...of, async generators, and streaming data patterns.


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI