Skip to content
intermediate Phase 5 · JavaScript Async & DOM

Promises & Async/Await

Master Promises, async/await, error handling, and Promise.all/race/settled patterns.

1h 15m
0 problems
Topic Progress 0%

Introduction to Promises

A Promise is an object representing the eventual completion or failure of an asynchronous operation. A Promise is in one of three states: pending (initial state), fulfilled (operation completed successfully), or rejected (operation failed). Promises provide a cleaner alternative to callback-based async code, avoiding callback hell. A Promise is created with a executor function that takes resolve and reject callbacks. Example: const promise = new Promise((resolve, reject) => { setTimeout(() => resolve('Done!'), 1000); });. You consume promises with .then() for success and .catch() for errors. Promise chaining allows sequential async operations without deep nesting: fetch(url).then(r => r.json()).then(data => process(data)).catch(err => handleError(err));. Promises are essential for modern JavaScript development, underpinning fetch, async/await, and many other APIs.

Promise.all, Promise.race, Promise.allSettled

Promise.all() takes an iterable of promises and returns a single promise that resolves when all input promises resolve, or rejects if any input promise rejects. Useful for parallel operations: const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);. If one fails, the whole thing fails fast. Promise.race() returns the result of the first settled promise (resolved or rejected). Useful for timeouts: const result = await Promise.race([fetch(url), timeout(5000)]);. Promise.allSettled() returns a promise that resolves after all input promises have settled (each with status 'fulfilled' or 'rejected'). Unlike Promise.all, it never rejects and gives you the outcome of every promise: const results = await Promise.allSettled([p1, p2, p3]); results.forEach(r => { if (r.status === 'fulfilled') handleSuccess(r.value); else handleFailure(r.reason); });. These methods are critical for coordinating multiple concurrent async tasks.

Async/Await Syntax

The async keyword declares a function that always returns a Promise. The await keyword pauses execution inside an async function until a Promise settles, then returns its result. Example: async function getUser(id) { const response = await fetch(/api/users/${id}); const user = await response.json(); return user; }. Async/await makes asynchronous code read like synchronous code, improving readability and debuggability. You can use try/catch blocks for error handling: try { const data = await fetchData(); process(data); } catch (err) { console.error(err); }. Sequential awaits execute one after another. For parallel execution, use Promise.all with destructuring: const [a, b] = await Promise.all([taskA(), taskB()]);. Await works at the top level in ES modules (top-level await). Async/await is the modern standard for writing asynchronous JavaScript and is built on top of Promises.

Error Handling Patterns

Robust error handling in async code prevents unhandled rejections and silent failures. Basic pattern: async function safeFetch(url) { try { const res = await fetch(url); if (!res.ok) throw new Error(HTTP ${res.status}); return await res.json(); } catch (err) { console.error('Fetch failed:', err.message); throw err; } }. For multiple independent operations, handle each individually: const [users, posts] = await Promise.allSettled([fetchUsers(), fetchPosts()]); users.status === 'rejected' && console.error(users.reason);. Retry pattern with exponential backoff: async function fetchWithRetry(fn, retries = 3) { for (let i = 0; i < retries; i++) { try { return await fn(); } catch (err) { if (i === retries - 1) throw err; await new Promise(r => setTimeout(r, 1000 * 2 ** i)); } } }. Always avoid swallowing errors silently - log or rethrow so failures are visible. Use .catch() on promise chains and try/catch with async/await.

Async Iteration and Generators

Async generators combine async functions and generators to produce values asynchronously over time. Declared with async function* and yield Promises. Example: async function* fetchPages(url) { let page = 1; while (true) { const res = await fetch(${url}?page=${page}); const data = await res.json(); yield data.items; if (!data.hasMore) break; page++; } }. Consume with for-await-of: for await (const items of fetchPages('/api/posts')) { items.forEach(render); }. This pattern is ideal for paginated APIs, streaming data, or any scenario where data arrives incrementally. The Symbol.asyncIterator protocol enables custom async iterables. Combined with ReadableStream, async iteration powers efficient streaming: const stream = response.body; for await (const chunk of stream) { process.stdout.write(chunk); }. Async iteration is widely used in Node.js streams, server-sent events, and real-time data processing.

Quiz

1. What does Promise.all() return if one of the input promises rejects?

Question 1 options

2. What is the output order of this code? console.log('A'); await Promise.resolve(); console.log('B'); setTimeout(() => console.log('C'), 0);

Question 2 options

3. Which method is best for running 10 API calls in parallel and collecting all results?

Question 3 options

Flashcards

Question

What is a Promise in JavaScript?

Answer

A Promise is an object representing the eventual completion or failure of an asynchronous operation. It has three states: pending, fulfilled, and rejected. Promises are consumed with .then() and .catch() methods and are the foundation of modern async JavaScript.

Question

What is the difference between async/await and .then() chains?

Answer

Both handle asynchronous results. Async/await makes code read like synchronous code using try/catch for errors. .then() chains use callback chaining. Async/await is generally more readable, easier to debug, and preferred for sequential async operations. Both return Promises.

Question

When would you use Promise.allSettled() instead of Promise.all()?

Answer

Use Promise.allSettled() when you need results from all promises regardless of whether individual ones fail. Promise.all() rejects immediately on any failure. allSettled() returns an array of objects with status ('fulfilled' or 'rejected') and value/reason for each promise.

Revision Notes

Key Takeaways

  • 1. Promises represent eventual completion of async operations with pending, fulfilled, and rejected states
  • 2. Promise.all() fails fast on first rejection; Promise.allSettled() waits for all and reports each outcome
  • 3. async/await syntax makes asynchronous code readable and allows using try/catch for error handling
  • 4. Async generators and for-await-of enable working with streams of data arriving over time

Interview Tips

  • Explain the Promise lifecycle and how to create, chain, and handle errors in promises
  • Compare Promise.all, Promise.race, Promise.allSettled, and Promise.any with use cases
  • Demonstrate async/await with proper error handling using try/catch
  • Discuss common pitfalls like unhandled rejections, sequential vs parallel awaits, and error swallowing

Cheat Sheet

Promise: pending → fulfilled/rejected. Promise.all(): resolves when all resolve, rejects on first reject. Promise.allSettled(): always resolves with status of each. Promise.race(): first settled wins. async function returns Promise; await pauses until settled. Always handle errors with try/catch or .catch().