JavaScript Event Loop Explained: How Asynchronous Operations with Promises and Async/Await Are Handled
everything you need to know about the JavaScript event loop, including call stack, task queues, promises, async/await, microtasks vs. macrotasks, execution order, and common pitfalls.
1. Event Loop Basics
- The call stack executes function frames in a LIFO order.
- Web APIs / Runtime (browser or Node) handle asynchronous operations (timers, I/O, DOM events).
- Completed async callbacks are queued in task queues for the event loop to pick up.
2. Macro-tasks vs. Micro-tasks
| Queue Type | Examples | When They Run |
|---|---|---|
| Macro-task | setTimeout, setInterval, I/O callbacks | After the current stack and all microtasks |
| Micro-task | Promise.then, MutationObserver | Immediately after current stack, before next macrotask |
3. Promises & Microtasks
Promisecallbacks always go into the microtask queue.- Microtasks run before the next macrotask, allowing promise chains to execute quickly.
4. Async/Await Under the Hood
asyncfunctions return a promise;awaitpauses execution until the promise settles.- Behind the scenes, each
awaityields to the event loop, scheduling the rest of the function as a microtask.
5. Practical Example
6. Common Pitfalls
- Blocking the Call Stack
Heavy synchronous work prevents the event loop from processing tasks. - Unresolved Promise Chains
Forgetting to return or catch can leave microtasks dangling. - setTimeout(fn, 0) ≠ Immediate
Even zero-delay timers wait for the next macrotask turn.
7. Summary Table
| Feature | Description |
|---|---|
| Call Stack | Executes frames LIFO |
| Macro-tasks | setTimeout, I/O – queued for next tick after microtasks |
| Micro-tasks | Promise.then, MutationObserver – run immediately after current stack |
| Async/Await | Sugar on promises; each await yields to microtask queue |
| Order of Execution | Call Stack → Microtasks → Render → Macro-tasks |
| Pitfalls | Blocking, dangling promises, misunderstanding of task ordering |
Leverage microtasks (
Promise,async/await) for smoother async flows.
❌ Don’t block the call stack with heavy sync work or assumesetTimeout(fn, 0)is instantaneous.