Javascript Async Await: The JavaScript Runtime Explained

Understanding Async/Await Under the Hood: The JavaScript Runtime Explained
8 views

async/await is syntactic sugar — but knowing what it desugars to explains every surprising behavior you encounter. Why does a resolved Promise run before setTimeout(fn,0)? Why does await never truly block? How does the engine know where to resume execution? Understanding the runtime makes you a better async programmer. This guide focuses on JavaScript async await, with the production trade-offs senior engineers need to make sound decisions.

⚡ TL;DR: async function returns Promise immediately. await suspends current function, saves stack frame, releases to event loop. Promise callbacks are microtasks — run before next macrotask. setTimeout is a macrotask. V8 uses coroutines for await resumption. Microtask queue drains completely before any macrotask.

async/await desugared — what it really is — JavaScript async await

// This async/await code:
async function fetchUser(id) {
  const response = await fetch('/api/users/'+id);
  const user = await response.json();
  return user;
}

// Is equivalent to this generator + Promise chain:
function fetchUser(id) {
  return new Promise((resolve,reject) => {
    const gen = (function*() {
      try {
        const response = yield fetch('/api/users/'+id);
        const user = yield response.json();
        resolve(user);
      } catch(err) { reject(err); }
    })();
    // Drive the generator forward on each yield resolution
    function step(value) {
      const result = gen.next(value);
      if(result.done) return;
      result.value.then(step,reject);
    }
    step();
  });
}

Event loop phases and microtasks

// Phase order:
// 1. Call stack (synchronous code)
// 2. Microtask queue: Promise callbacks, queueMicrotask()
// 3. Macrotask queue: setTimeout, setInterval, I/O
// Microtask queue DRAINS COMPLETELY between macrotasks

console.log('1');           // Call stack
setTimeout(()=>console.log('2'),0); // Macrotask queue
Promise.resolve().then(()=>console.log('3')); // Microtask queue
Promise.resolve()
  .then(()=>console.log('4'))
  .then(()=>console.log('5')); // Two microtasks chained
console.log('6');           // Call stack
// Output: 1, 6, 3, 4, 5, 2
// 3,4,5 = microtasks ALL drain before macrotask '2'

Why await never blocks

// await pauses the FUNCTION but not the thread
async function main() {
  console.log('A');           // Runs immediately
  const data = await fetch('/api/data'); // Suspends main()
                              // V8 saves the stack frame
                              // Event loop continues!
  console.log('C');           // Resumes after fetch resolves
}
main();
console.log('B');             // Runs WHILE main is suspended
// Output: A, B, C
// JavaScript is single-threaded — await releases control
// Node.js event loop can process other I/O while waiting

Microtask starvation — the hidden danger

// Infinite microtask loop starves event loop!
function bad() {
  Promise.resolve().then(bad); // Creates endless microtasks!
}
bad();
// Event loop never processes I/O, timers, or anything else!
// setTimeout callbacks never run

// Same problem with recursive async:
async function flood() {
  while(true) await Promise.resolve(); // Blocks event loop!
}
// Fix: use setImmediate() to yield to event loop occasionally
async function better(items) {
  for(let i=0; isetImmediate(r)); // Yield!
  }
}
  • ✅ Promise microtasks execute before setTimeout macrotasks
  • ✅ Microtask queue drains completely before next macrotask
  • ✅ await suspends function frame, releases thread to event loop
  • ✅ async function always returns a Promise (even if you return a value)
  • ❌ Never create infinite microtask chains — starves event loop
  • ❌ Never mix callbacks and promises in same function

External reference: MDN Microtask guide.

Recommended Reading

→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.

→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.

Affiliate links. We earn a small commission at no extra cost to you.

Related on CheatCoders: Node.js event-loop blocking fixes, JavaScript Promises advanced patterns, and Node.js clustering and worker threads.

Free Weekly Newsletter

🚀 Join 2,000+ Senior Developers

Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.

✓ No spam✓ Unsubscribe anytime✓ Expert-level only

Related: JavaScript Async/Await: Every Pattern, Pitfall, and Performance Trick · TypeScript for JavaScript Developers: The Migration Guide That Actually Works · JavaScript Closures, Scope, and the Module Pattern: The Complete Guide

Related: JavaScript Array Methods: Every Method Explained With Real Examples · Design Patterns in JavaScript: The Gang of Four Patterns Every Senior Dev Uses · Webpack and Vite: Modern JavaScript Bundling Explained for Application Developers

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.