Async / Await

Write async code that looks and feels synchronous. The cleanest pattern.

async Functions

Mark a function as async — it always returns a Promise.

// Regular function:
function greet() {
  return "Hello";
}
greet(); // "Hello"

// Async function:
async function greetAsync() {
  return "Hello";
}
greetAsync(); // Promise { "Hello" }

An async function ALWAYS returns a Promise. Even if you return a plain value, it's automatically wrapped in Promise.resolve().

1 / 3

The await Keyword

Pause execution until a Promise resolves.

async function loadUser() {
  console.log("Start");
  
  const response = await fetch("/api/user");
  // ⏸️ Pauses here until fetch resolves
  
  const user = await response.json();
  // ⏸️ Pauses here until .json() resolves
  
  console.log("Got user:", user.name);
  return user;
}

await pauses the async function (not the whole program!) until the Promise settles. It then unwraps the resolved value.

💡 await only works inside async functions
1 / 2

💡 await doesn't block the main thread. Other code (event handlers, other async functions) keeps running while this function is paused.

Sequential vs Parallel

Don't accidentally make parallel operations sequential.

// ❌ SEQUENTIAL (slow) — each waits for the previous:
async function loadAll() {
  const users = await fetchUsers();    // 1s
  const posts = await fetchPosts();    // 1s
  const comments = await fetchComments(); // 1s
  // Total: ~3 seconds 😬
}

// ✅ PARALLEL (fast) — all start at once:
async function loadAll() {
  const [users, posts, comments] = await Promise.all([
    fetchUsers(),    // 1s ─┐
    fetchPosts(),    // 1s ─┤ all run simultaneously
    fetchComments()  // 1s ─┘
  ]);
  // Total: ~1 second 🚀
}

Sequential

Use when each step depends on the previous result

Parallel

Use when operations are independent of each other

// Mixed: some sequential, some parallel
async function loadDashboard(userId) {
  // Sequential (posts need user first):
  const user = await fetchUser(userId);
  
  // Parallel (these are independent):
  const [posts, notifications] = await Promise.all([
    fetchPosts(user.id),
    fetchNotifications(user.id)
  ]);
  
  return { user, posts, notifications };
}

Error Handling

try/catch works perfectly with await.

async function loadData() {
  try {
    const res = await fetch("/api/data");
    
    if (!res.ok) {
      throw new Error(`HTTP error: ${res.status}`);
    }
    
    const data = await res.json();
    return data;
  } catch (error) {
    // Catches: network errors, HTTP errors, JSON parse errors
    console.error("Failed to load:", error.message);
    return null; // fallback value
  } finally {
    // Always runs — perfect for cleanup:
    hideLoadingSpinner();
  }
}

⚠️ If you don't use try/catch, rejected promises in async functions become unhandled rejections. Always handle errors!

Common Patterns

Useful async/await patterns you'll use often.

// Pattern 1: Retry with delay
async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url);
      if (res.ok) return await res.json();
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}
// Pattern 2: Process array items sequentially
async function processItems(items) {
  const results = [];
  for (const item of items) {
    const result = await processItem(item);
    results.push(result);
  }
  return results;
}

// Pattern 3: Process array items in parallel
async function processItemsParallel(items) {
  return Promise.all(items.map(item => processItem(item)));
}
// Pattern 4: Async IIFE (immediately invoked)
(async () => {
  const data = await fetchData();
  console.log(data);
})();

// Pattern 5: Top-level await (ES modules)
// In .mjs files or <script type="module">:
const config = await loadConfig();

FAQ

Common questions about async/await.