Skip to main content

Async/Await

Wait for the coffee, then continue

The Coffee Shop Analogy

You walk into a busy coffee shop and order a latte.

What you DON'T do:

  • Stand frozen at the counter
  • Potentially block people behind you
  • Stare at the barista until your drink is ready

What you ACTUALLY do:

  • Place your order
  • Step aside
  • Check your phone, chat with friends
  • Wait for your name to be called
  • Pick up your drink

You don't waste time waiting - you do other things while yours is being prepared!

Async/await lets your code work the same way.

Instead of blocking the entire thread while waiting for slow operations (like network requests), your code says: "Start this task, notify me when it's done" and continues doing other things.

More precisely (in JavaScript): await pauses only the current async function and returns control to the event loop, so other work can continue while the promise is pending.


Why Do We Need Async/Await?

The Problem: Slow Operations

Some things take time:

  • Fetching data from the internet
  • Reading files from disk
  • Querying a database
  • Waiting for user input

Without async:

If you use blocking (synchronous) waits, your code would freeze while waiting. Imagine a website that locks up for a few seconds every time it loads data. Users would leave!

In many JavaScript apps, the equivalent problem is “tying up the main thread” or “not yielding back to the event loop,” which can make the UI or server feel unresponsive.

With async:

Your code keeps running. When the slow operation finishes, it handles the result. No freezing.

Real-world analogy:

Without AsyncWith Async
Restaurant waiter takes ONE order, waits for it to cook, serves it, then takes the next orderWaiter takes MANY orders, kitchen works on multiple dishes at once, waiter serves as dishes are ready

The Core Concept

Async/await is about two ideas:

  1. async - Mark a function as "this might wait for something"
  2. await - "Pause this async function until this finishes, but don't freeze the whole program"
async function orderCoffee() {
  // Other code keeps running during the wait
  const coffee = await makeCoffee();  // Pauses HERE
  return coffee;  // Continues after coffee is ready
}

Think of await like saying "I'll wait for this, but other code can keep running."

Important: await doesn't make work parallel by itself. If you await three independent operations one-by-one, they'll run sequentially. To run independent operations concurrently, start them first and then await them together (for example with Promise.all).


How It Works Step by Step

Step 1: Mark the function as async

This tells JavaScript: "This function may need to wait for things."

async function fetchUserData() {
  // This function can use 'await'
}

Step 2: Use await when calling slow operations

async function fetchUserData() {
  const response = await fetch("/api/user"); // Wait for response
  const data = await response.json(); // Wait for parsing
  return data;
}

Step 3: Handle the result

The function returns a Promise. When everything inside completes, the Promise resolves.


The Problem Async/Await Solves

Before async/await, we had "callback hell":

getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      finallyDoSomething(c, function(d) {
        // Nested nightmare!
      });
    });
  });
});

With async/await, it's flat and readable:

const a = await getData();
const b = await getMoreData(a);
const c = await getEvenMoreData(b);
const d = await finallyDoSomething(c);

Same logic, but you can actually read it!


Error Handling

Things go wrong. Servers crash. Networks fail. You need to handle errors.

Use try/catch (just like regular JavaScript):

async function fetchData() {
  try {
    const response = await fetch("/api/data");
    return await response.json();
  } catch (error) {
    console.error("Something went wrong:", error);
    return null; // Return fallback value
  }
}

What can go wrong:

  • Network errors (no internet)
  • Server errors (500 status)
  • Invalid data (can't parse JSON)

Plan for failure.


Sequential vs Parallel

Sequential: One after another

Task 1 (takes time)
       ↓
Task 2 (takes time)
       ↓
Task 3 (takes time)

Total: roughly the sum of each task

Parallel: All at once

Task 1 (takes time) ─┐
Task 2 (takes time) ─┼─→ All finish around when the slowest finishes
Task 3 (takes time) ─┘

Total: roughly the slowest task

When to use which:

SequentialParallel
Task 2 needs result of Task 1Tasks are independent
Order mattersOrder doesn't matter

Parallel example with Promise.all:

// Start all at once, then wait for all to finish
const [user, posts, comments] = await Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments(),
]);

Real-World Examples

1. Fetching User Data

When user logs in:
  1. Fetch user profile
  2. Fetch their preferences
  3. Fetch their notifications
  4. Show personalized dashboard

2. Filling a Shopping Cart

User clicks "Add to Cart":
  1. Check if item is in stock (API call)
  2. If yes, add to cart (API call)
  3. Update cart count in UI

3. Loading a Web Page

Page loads:
  1. Fetch page content
  2. Fetch user settings
  3. Fetch ads (parallel)
  4. Render everything together

Common Mistakes

Mistake 1: Forgetting 'await'

Without await, you get a Promise object, not the actual value:

// ❌ Wrong
const data = fetch("/api/data"); // data is a Promise!

// ✓ Correct
const response = await fetch("/api/data");
const data = await response.json(); // data is the actual value

Mistake 2: Using await in forEach

forEach doesn't wait properly:

// ❌ Doesn't work as expected
items.forEach(async (item) => {
  await processItem(item); // Fires all at once!
});

// ✓ Use for...of instead
for (const item of items) {
  await processItem(item); // Waits for each
}

In general: `forEach` doesn't wait for the async callbacks to finish. If you want sequential processing, use `for...of`. If you want concurrent processing, build an array of promises and `await Promise.all(...)`.

Mistake 3: Not Handling Errors

Unhandled async errors can bubble up in surprising ways:

// ❌ Dangerous
const data = await fetch("/api/data"); // What if it fails?

// ✓ Better
try {
  const data = await fetch("/api/data");
} catch (error) {
  handleError(error);
}

Note: `fetch` typically resolves even for HTTP error status codes (like 404/500). You usually need to check `response.ok` and handle error responses explicitly.

Mistake 4: Making Everything Sequential When It Could Be Parallel

// ❌ Slow - waits for each request one-by-one
const users = await fetchUsers();
const products = await fetchProducts();
const orders = await fetchOrders();

// ✓ Faster - runs requests concurrently
const [users, products, orders] = await Promise.all([
  fetchUsers(),
  fetchProducts(),
  fetchOrders(),
]);

FAQ

Q: What's the difference between async/await and Promises?

Async/await is built on top of Promises. It's mostly a cleaner syntax for working with them. Behind the scenes, async functions return Promises.

Q: Can I use await outside of an async function?

In many modern JavaScript environments (ES modules), yes — "top-level await." In other setups, you typically wrap it in an async function.

Q: What happens if I forget 'async' but use 'await'?

In most cases you'll get a syntax error. Typically, the function needs to be marked async to use await inside it.

Q: How do I run multiple async operations and get all results?

Use Promise.all() to run them in parallel:

const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);

Q: What if I want to wait for some but continue if others fail?

Use Promise.allSettled() - it gives you the status of each Promise:

const results = await Promise.allSettled([fetch1(), fetch2()]);
// Results include both successes and failures

Q: Does async/await change runtime behavior compared to callbacks?

Often there's no meaningful runtime difference - it's largely syntax over Promises. The main benefit is readability and maintainability.


Summary

Async/await makes asynchronous JavaScript code readable and maintainable. It lets your code wait for slow operations without freezing.

Key Takeaways:

  • async marks a function as asynchronous
  • await pauses until a Promise resolves
  • Use try/catch for error handling
  • Use Promise.all() for parallel operations
  • Don't forget await - you'll get Promises, not values
  • forEach doesn't work with await - use for...of
  • Handle errors

Think of async/await as the polite way to wait - you're patient, but you don't block anyone else while waiting!

Leave a Comment

Comments (0)

Be the first to comment on this concept.

Comments are approved automatically.