Skip to main content

Async JavaScript

Event Loop

JavaScript is single-threaded but handles async operations using the event loop.

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│ Call Stack │ ←── │ Event Loop │ ←── │ Task Queue │
└─────────────┘ └──────────────┘ └─────────────┘
┌──────────────────┐
│ Microtask Queue │
└──────────────────┘

Execution Order

  1. Execute synchronous code on the call stack.
  2. Process all microtasks (Promises, queueMicrotask).
  3. Process one macrotask (setTimeout, setInterval, I/O).
  4. Repeat.
console.log("1");

setTimeout(() => console.log("2"), 0);

Promise.resolve().then(() => console.log("3"));

console.log("4");

// Output: 1, 4, 3, 2
// Sync first → microtask (Promise) → macrotask (setTimeout)

Callbacks

function fetchData(callback) {
setTimeout(() => {
callback("data");
}, 1000);
}

// Callback hell
fetchData((data) => {
processData(data, (result) => {
saveData(result, (response) => {
// deeply nested — hard to read
});
});
});

Promises

A Promise represents a value that may be available now, later, or never.

const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000);
});

promise
.then((value) => console.log(value))
.catch((err) => console.error(err))
.finally(() => console.log("cleanup"));

Promise Static Methods

// All must resolve
Promise.all([p1, p2, p3]).then((results) => {});

// First to settle
Promise.race([p1, p2, p3]).then((fastest) => {});

// All settle (no rejection)
Promise.allSettled([p1, p2]).then((results) => {});

// First to resolve (ignores rejections)
Promise.any([p1, p2]).then((first) => {});

Async / Await

Syntactic sugar over Promises — makes async code look synchronous.

async function getData() {
try {
const response = await fetch("/api/data");
const data = await response.json();
return data;
} catch (error) {
console.error("Failed:", error);
}
}

Parallel Execution

// Sequential — slow
const a = await fetchA();
const b = await fetchB();

// Parallel — fast
const [a, b] = await Promise.all([fetchA(), fetchB()]);

Key Takeaways

  • JavaScript uses an event loop to handle async operations on a single thread.
  • Microtasks (Promises) run before macrotasks (setTimeout).
  • Promises solve callback hell; async/await makes Promises readable.
  • Use Promise.all() for parallel execution.