Back to JavaScript
JavaScript
medium
mid

How are asynchronous tasks executed in JavaScript?

Duplicate-style question on the event loop and async execution: JS hands async work to Web APIs, which queue callbacks (microtask for Promises, macrotask for timers/events) on completion; the event loop drains all microtasks then runs one macrotask whenever the call stack is empty.

5 min read·~10 min to think through

(This is the same core topic as the other event-loop questions — here's the focused version on how async tasks get executed.)

The flow

  1. You call an async API. setTimeout, fetch, addEventListener — these are Web APIs provided by the browser, not the JS engine. JS registers them and immediately returns; the stack keeps going.
  2. The host does the work off-thread. The browser runs the timer, performs the network request, watches for the event. The JS thread is free.
  3. On completion, the callback is queued.
  • Promise reactions / await continuations → microtask queue.
  • Timers, events, I/O → macrotask (task) queue.
  1. The event loop schedules it. When the call stack is empty: drain all microtasks → (maybe render) → run one macrotask → repeat.

Code trace

js
console.log("1");
setTimeout(() => console.log("2"), 0);   // browser timer -> macrotask
fetch("/x").then(() => console.log("3"));// browser network -> microtask on resolve
console.log("4");
// 1, 4, then 3 (when response arrives, as a microtask) ... 2 may print before 3
// if the network is slow — ordering between an I/O macrotask source and a
// pending request depends on when the request actually completes.

(For a guaranteed-resolved Promise vs setTimeout, the Promise always wins. For a real fetch, it depends on network timing — but the callback, once queued, is still a microtask.)

Key takeaways

  • JS itself is synchronous and single-threaded; concurrency comes from the host + the queues.
  • The callback only runs when the stack is empty — a long sync block delays every pending async callback.
  • Microtasks outrank macrotasks every loop iteration.

Senior framing

Frame it as delegation + scheduling: the engine delegates async work to the environment and the event loop schedules the results back onto the single thread, prioritizing microtasks. That model explains both responsiveness and jank.

Follow-up questions

  • If the main thread is blocked, when does a completed fetch callback run?
  • Why are timers macrotasks but Promise resolutions microtasks?
  • What runs the actual network request?

Common mistakes

  • Believing the JS engine performs the network/timer work itself.
  • Assuming async = parallel JS.
  • Ignoring microtask vs macrotask priority.

Edge cases

  • A blocked main thread queues callbacks but runs none until it unblocks.
  • fetch resolution timing depends on the network; the queued callback is still a microtask.

Real-world examples

  • fetch, event listeners, setTimeout, requestIdleCallback.

Related questions