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.
js
1console.log('Start'); 2setTimeout(() => console.log('Timeout'), 0); 3console.log('End'); 4// Logs: Start, End, Timeout

2. Macro-tasks vs. Micro-tasks

Queue TypeExamplesWhen They Run
Macro-tasksetTimeout, setInterval, I/O callbacksAfter the current stack and all microtasks
Micro-taskPromise.then, MutationObserverImmediately 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.
js
1console.log('A'); 2Promise.resolve().then(() => console.log('B')); 3console.log('C'); 4// Logs: A, C, B

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.
js
1async function foo() { 2 console.log('1'); 3 await null; 4 console.log('2'); 5} 6foo(); 7console.log('3'); 8// Logs: 1, 3, 2

5. Practical Example

js
1async function fetchData() { 2 try { 3 const response = await fetch('/api/data'); 4 const json = await response.json(); 5 console.log('Data:', json); 6 } catch (err) { 7 console.error('Error:', err); 8 } 9} 10fetchData(); 11console.log('After fetch'); 12// Possible logs: "After fetch", then "Data: …"

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

FeatureDescription
Call StackExecutes frames LIFO
Macro-taskssetTimeout, I/O – queued for next tick after microtasks
Micro-tasksPromise.then, MutationObserver – run immediately after current stack
Async/AwaitSugar on promises; each await yields to microtask queue
Order of ExecutionCall Stack → Microtasks → Render → Macro-tasks
PitfallsBlocking, 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 assume setTimeout(fn, 0) is instantaneous.