JavaScript Async Programming

Some things take time: network requests, timers, reading files. JavaScript is single-threaded, so instead of waiting (and freezing the page), it uses callbacks scheduled for later — organized through promises and async/await.

Feel the event loop

Run this and watch the order — predict it before pressing play:

The event loop model: synchronous code → microtasks (promises) → macrotasks (timers). Interviewers love this ordering.

Promises

A promise represents a future value. Here's a fake API — same shape as a real fetch() call, just delayed instead of networked:

async / await

async functions let you consume promises with await — asynchronous code that reads top-to-bottom:

JavaScript
async function loadUser(id) {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();     // also a promise — bodies stream in
}

Errors are caught with regular try/catch:

JavaScript
try {
  const user = await loadUser(7);
  render(user);
} catch (err) {
  showError(err.message);
}

Parallel vs sequential

Two awaits in a row wait for one THEN the other. When calls are independent, start them together and await both:

JavaScript
// Sequential: 300ms + 300ms = 600ms
const a = await fetch("/api/a").then(r => r.json());
const b = await fetch("/api/b").then(r => r.json());

// Parallel: max(300, 300) = 300ms
const [a2, b2] = await Promise.all([
  fetch("/api/a").then(r => r.json()),
  fetch("/api/b").then(r => r.json()),
]);

Promise combinators:

Combinator Resolves when
Promise.all all succeed — rejects fast on FIRST failure
Promise.allSettled all settle — never rejects, inspect each status
Promise.race first to settle wins (timeouts)
Promise.any first success

Production patterns

Retry with exponential backoff, and cancel slow requests:

JavaScript
async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const r = await fetch(url);
      if (r.ok) return r.json();
    } catch {}
    await new Promise(res => setTimeout(res, 1000 * 2 ** i));  // 1s, 2s, 4s
  }
  throw new Error(`Failed after ${retries} attempts`);
}

const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);   // hard timeout
await fetch(url, { signal: controller.signal });

Knowledge Check

4 questions — test your understanding

1

Output order: log('1'); setTimeout(() => log('2'), 0); Promise.resolve().then(() => log('3')); log('4');

2

Promise.all rejects when...

3

What does await actually do?

4

Why is response.json() awaited?