Async/Await

Node.js Fundamentals
Course 1 Β· Chapter 4 Β· Async/Await

⏳ Async/Await

async/await is syntactic sugar built on top of promises. It lets you write asynchronous code that looks and feels like synchronous code β€” no more chains of .then() calls. This makes async code cleaner, easier to read, and easier to debug. This chapter covers async functions, await expressions, error handling with try/catch, and concurrent operations with async/await.

πŸ”§ Async Functions

An async function always returns a promise:

async function greet() {
  return 'Hello!';
}

// This is equivalent to:
function greet() {
  return Promise.resolve('Hello!');
}

// Both return a promise
greet().then(msg => console.log(msg)); // Hello!

The value you return from an async function is automatically wrapped in a resolved promise.

⏸️ The await Keyword

await pauses execution until a promise settles. It can only be used inside an async function:

async function fetchUser(id) {
  // Pause here until promise resolves
  const user = await getUserFromDB(id);
  console.log('User:', user);
  return user;
}

// Call the async function
const result = fetchUser(1); // Returns a promise
result.then(user => console.log(user));

πŸ“Š Async/Await vs Promises

Side-by-Side Comparison

Promise Chains
fetchUser(1)
  .then(user => {
    console.log(user);
    return fetchOrders(user.id);
  })
  .then(orders => {
    console.log(orders);
  })
  .catch(error => {
    console.error(error);
  });
Async/Await
async function load() {
  try {
    const user = await fetchUser(1);
    console.log(user);
    const orders = await fetchOrders(user.id);
    console.log(orders);
  } catch (error) {
    console.error(error);
  }
}

load();

Async/await reads like synchronous code, making it much easier to follow.

πŸ›‘οΈ Error Handling with Try/Catch

Use try/catch to handle errors in async functions:

async function fetchUserData(id) {
  try {
    const user = await fetchUser(id);
    const orders = await fetchOrders(user.id);
    return { user, orders };
  } catch (error) {
    console.error('Failed to load data:', error.message);
    return null; // Return fallback value
  }
}

// Call it
await fetchUserData(1);

If any await expression rejects, execution jumps to catch.

⚑ Sequential vs Concurrent Execution

Sequential (Slow)

async function sequential() {
  const user = await fetchUser(1);      // 1000ms
  const posts = await fetchPosts(user.id); // 1000ms
  return { user, posts };                // Total: 2000ms
}

Concurrent (Fast)

async function concurrent() {
  // Start both promises, then await results
  const userPromise = fetchUser(1);     // Start here
  const postsPromise = fetchPosts(1);    // Start here

  // Wait for both
  const user = await userPromise;
  const posts = await postsPromise;
  return { user, posts };                 // Total: ~1000ms
}

// Or use Promise.all():
async function concurrent() {
  const [user, posts] = await Promise.all([
    fetchUser(1),
    fetchPosts(1)
  ]);
  return { user, posts };
}

🎯 Arrow Async Functions

const fetchAndLog = async (id) => {
  const user = await fetchUser(id);
  console.log(user);
};

// Usage
await fetchAndLog(1);

πŸš€ Top-Level Await (Modern Node.js)

In recent Node.js versions, you can use await at the top level of a module (no async function needed):

// Old way: wrap in async function
async function main() {
  const data = await fetchData();
  console.log(data);
}
main();

// New way: use await directly
const data = await fetchData();
console.log(data);

πŸ’» Coding Challenges

Challenge 1: Convert Promise Chain to Async/Await

Take a promise chain and rewrite it using async/await. Compare readability.

Goal: Practice converting between async patterns.

β†’ Solution

Challenge 2: Sequential vs Concurrent Operations

Create an async function that fetches three datasets. Implement both sequential and concurrent versions. Measure the time difference.

Goal: Understand performance implications of await placement.

β†’ Solution

Challenge 3: Error Handling with Try/Catch

Create an async function with multiple awaits and error handling. Show recovery, logging, and finally blocks.

Goal: Master error handling in async code.

β†’ Solution

πŸ’‘ Async/Await Best Practices

1. Don't forget await: Forgetting await returns a promise instead of the value.

2. Use concurrent execution: Start independent operations before awaiting them.

3. Always handle errors: Use try/catch or .catch() on the returned promise.

4. Avoid await in loops if they're independent: Use Promise.all() instead.

🎯 What's Next

Now that you can handle asynchronous code cleanly with async/await, we'll explore File System Operations β€” reading, writing, and managing files in Node.js using these async patterns.