JS

Async Programming

JavaScript · 9 entries

Promise Basics

syntax
new Promise((resolve, reject) => { ... })
promise.then(onFulfilled, onRejected)
promise.catch(onRejected)
promise.finally(onFinally)
example
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

delay(1000)
  .then(() => console.log("1 second passed"))
  .catch(err => console.error(err))
  .finally(() => console.log("Done"));

Note A Promise is always in one of three states: pending, fulfilled, or rejected. Once settled, it cannot change state.

async / await

syntax
async function name() {
  const result = await promise;
}
example
async function loadUser(userId) {
  const response = await fetch(`/api/users/${userId}`);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const user = await response.json();
  return user;
}

// Usage
try {
  const user = await loadUser(42);
  console.log(user.name);
} catch (err) {
  console.error("Failed to load user:", err.message);
}

Note await pauses execution inside an async function until the promise settles. Top-level await works in ES modules. Always handle errors with try/catch or .catch().

Promise.all()

syntax
Promise.all([p1, p2, p3])
example
async function loadDashboard(userId) {
  const [profile, posts, notifications] = await Promise.all([
    fetch(`/api/users/${userId}`).then(r => r.json()),
    fetch(`/api/posts?author=${userId}`).then(r => r.json()),
    fetch(`/api/notifications/${userId}`).then(r => r.json()),
  ]);
  return { profile, posts, notifications };
}

Note Runs all promises in parallel and resolves when ALL succeed. Rejects immediately if ANY promise rejects -- other pending promises are not cancelled, just ignored.

Promise.allSettled()

syntax
Promise.allSettled([p1, p2, p3])
example
const results = await Promise.allSettled([
  fetch("/api/service-a"),
  fetch("/api/service-b"),
  fetch("/api/service-c"),
]);

const successful = results.filter(r => r.status === "fulfilled");
const failed = results.filter(r => r.status === "rejected");
console.log(`${successful.length} succeeded, ${failed.length} failed`);

Note Never rejects. Each result is { status: "fulfilled", value } or { status: "rejected", reason }. Use when you want all results regardless of failures.

Promise.race()

syntax
Promise.race([p1, p2, p3])
example
function fetchWithTimeout(url, ms = 5000) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("Timeout")), ms)
  );
  return Promise.race([fetch(url), timeout]);
}

try {
  const response = await fetchWithTimeout("/api/data", 3000);
  console.log(await response.json());
} catch (err) {
  console.error(err.message); // "Timeout" or network error
}

Note Settles as soon as the first promise settles (fulfilled OR rejected). Common use: implementing timeouts.

Promise.any()

syntax
Promise.any([p1, p2, p3])
example
async function fetchFromMirrors(path) {
  return Promise.any([
    fetch(`https://mirror1.example.com${path}`),
    fetch(`https://mirror2.example.com${path}`),
    fetch(`https://mirror3.example.com${path}`),
  ]);
}
// Returns the first successful response; ignores individual failures

Note Resolves with the first fulfilled promise. Only rejects if ALL promises reject (with an AggregateError). The opposite of Promise.race() for error handling.

Fetch API

syntax
const response = await fetch(url, options);
example
// GET request
const res = await fetch("/api/products");
const products = await res.json();

// POST request
const created = await fetch("/api/products", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Widget", price: 29.99 }),
});

if (!created.ok) throw new Error(`HTTP ${created.status}`);

Note fetch() does NOT reject on HTTP error status codes (404, 500). Always check response.ok or response.status. The body can only be consumed once.

AbortController

syntax
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort();
example
async function searchWithCancel(query) {
  const controller = new AbortController();

  // Auto-cancel after 5 seconds
  const timeoutId = setTimeout(() => controller.abort(), 5000);

  try {
    const res = await fetch(`/api/search?q=${query}`, {
      signal: controller.signal,
    });
    clearTimeout(timeoutId);
    return await res.json();
  } catch (err) {
    if (err.name === "AbortError") {
      console.log("Request was cancelled");
    } else {
      throw err;
    }
  }
}

Note Essential for cancelling stale requests (e.g., in search-as-you-type). The AbortSignal can be shared across multiple fetch calls to cancel them all at once.

Async Error Handling Patterns

syntax
try { await ... } catch (err) { ... }
example
// Wrapper to avoid repetitive try/catch
async function safeAwait(promise) {
  try {
    const data = await promise;
    return [data, null];
  } catch (err) {
    return [null, err];
  }
}

const [user, error] = await safeAwait(fetch("/api/me").then(r => r.json()));
if (error) {
  console.error("Failed:", error.message);
} else {
  console.log("User:", user.name);
}

Note Unhandled promise rejections crash Node.js and show warnings in browsers. Always catch async errors. The tuple pattern [data, error] is popular for cleaner control flow.