Lesson 9 of 49 intermediate

Async/Await & Promises

Handling Work That Takes Time

Open interactive version (quiz + challenge)

Real-world analogy

A Promise is like ordering pizza. You get a receipt (Promise) immediately. Pizza isn't ready yet — it's PENDING. Later: Pizza arrives (FULFILLED) or they call saying it's cancelled (REJECTED). async/await is a cleaner way to handle pizza than the old .then() way!

What is it?

Promises represent values that don't exist yet but will in the future. async/await is syntax sugar that makes Promises feel like normal code. It's the standard way to handle asynchronous work (API calls, file reads, timeouts).

Real-world relevance

Every API call is a Promise. React Query wraps Promises. You'll use async/await multiple times every day!

Key points

Code example

// Promise states
const promise = fetch("https://api.example.com/user");
// PENDING: waiting for response
// then either:
// FULFILLED: response arrived
// REJECTED: network error

// async/await — the clean way! ✨
async function getUser() {
  try {
    const response = await fetch("https://api.example.com/user");
    const user = await response.json();
    console.log(user.name);
    return user;
  } catch (error) {
    console.error("Failed:", error);
    // handle error
  } finally {
    console.log("Request done!");  // always runs
  }
}

// Call async function
const user = await getUser();

// Multiple async operations
async function loadDashboard() {
  const user = await getUser(1);          // wait for user
  const posts = await getPosts(user.id);  // THEN wait for posts
  // Total time: 2sec + 2sec = 4 seconds ⏳
  return { user, posts };
}

// Promise.all — parallel execution! 🚀
async function loadDashboardFast() {
  const [user, posts, comments] = await Promise.all([
    getUser(1),
    getPosts(1),
    getComments(1)
  ]);
  // All run at SAME TIME!
  // Total time: 2 seconds (instead of 6!) ⚡
  return { user, posts, comments };
}

// .then/.catch (old way — avoid!)
fetch("https://api.example.com/user")
  .then(res => res.json())
  .then(user => console.log(user.name))
  .catch(error => console.error(error));
// Gets messy with more steps!

// Throwing errors in async
async function doSomething() {
  if (!user) {
    throw new Error("User not found!");  // Promise rejects
  }
  return "Success!";  // Promise fulfills
}

// Promise.race — first to finish wins
const result = await Promise.race([
  fetchFromServer1(),
  fetchFromServer2(),
  timeoutAfter(5000)
]);
// whichever completes first is returned!

Line-by-line walkthrough

  1. 1. Promise states
  2. 2. Declaring a variable
  3. 3. PENDING: waiting for response
  4. 4. then either:
  5. 5. FULFILLED: response arrived
  6. 6. REJECTED: network error
  7. 7.
  8. 8. async/await — the clean way! ✨
  9. 9. Declaring a function
  10. 10. Opening try block for error handling
  11. 11. Declaring a variable
  12. 12. Declaring a variable
  13. 13. Printing output to the console
  14. 14. Returning a value
  15. 15. Catching any errors from the try block
  16. 16.
  17. 17. handle error
  18. 18. Finally block - runs regardless of success or failure
  19. 19. Printing output to the console
  20. 20. Closing block
  21. 21. Closing block
  22. 22.
  23. 23. Call async function
  24. 24. Declaring a variable
  25. 25.
  26. 26. Multiple async operations
  27. 27. Declaring a function
  28. 28. Declaring a variable
  29. 29. Declaring a variable
  30. 30. Total time: 2sec + 2sec = 4 seconds ⏳
  31. 31. Returning a value
  32. 32. Closing block
  33. 33.
  34. 34. Promise.all — parallel execution! 🚀
  35. 35. Declaring a function
  36. 36. Declaring a variable
  37. 37.
  38. 38.
  39. 39.
  40. 40.
  41. 41. All run at SAME TIME!
  42. 42. Total time: 2 seconds (instead of 6!) ⚡
  43. 43. Returning a value
  44. 44. Closing block
  45. 45.
  46. 46. .then/.catch (old way — avoid!)
  47. 47.
  48. 48. Method chaining on the previous expression
  49. 49. Method chaining on the previous expression
  50. 50. Method chaining on the previous expression
  51. 51. Gets messy with more steps!
  52. 52.
  53. 53. Throwing errors in async
  54. 54. Declaring a function
  55. 55. Conditional check
  56. 56. Throwing an error
  57. 57. Closing block
  58. 58. Returning a value
  59. 59. Closing block
  60. 60.
  61. 61. Promise.race — first to finish wins
  62. 62. Declaring a variable
  63. 63.
  64. 64.
  65. 65.
  66. 66.
  67. 67. whichever completes first is returned!

Spot the bug

function getUser() {
  const user = await fetch("/api/user");
  return user.json();
}
Need a hint?
What keyword is missing from the function declaration?
Show answer
You can only use 'await' inside an 'async' function, but this function is not declared as async. Fix: change 'function getUser()' to 'async function getUser()'.

Explain like I'm 5

Imagine ordering a toy online. You don't get it right away - you get a tracking number (Promise). You can wait by the door all day (old way) or go play and someone tells you when it arrives (async/await). Much better!

Fun fact

Promises were inspired by other languages' Futures (Dart) and Completable Futures (Java). async/await was added to make Promises feel like normal synchronous code!

Hands-on challenge

Create async functions that each take 1 second. Run 5 of them sequentially (measures time), then in parallel with Promise.all. How much faster?

More resources

Open interactive version (quiz + challenge) ← Back to course: Full-Stack Playbook