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
Promise
callbacks always go into the microtask queue.- Microtasks run before the next macrotask, allowing promise chains to execute quickly.
4. Async/Await Under the Hood
async
functions return a promise;await
pauses execution until the promise settles.- Behind the scenes, each
await
yields 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.