Skip to main content

Command Palette

Search for a command to run...

Understanding Async/Await in JavaScript

Updated
•4 min read•View as Markdown

JavaScript is famous for making websites interactive but there is one thing every developer struggles with is handling asynchronous operations.

Imagine, you order chai at a shop, While chaiwala prepares your chai, you don’t stand there. You chat with friends, check your phone. Once chai is ready, you continue.

Thats how JavaScript handles async tasks.

Before async/await, developers used callbacks and later Promises. But complex async code quickly became hard to read. To solve this, JavaScript introduced async/await which was a cleaner and more readable way to work with asynchronous code.

Why Async/Await Was Introduced

Before async/await, developers used Promises like this:

fetchData()
  .then((data) => {
    return processData(data);
  })
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  });

This works fine for small tasks.

But when multiple async operations are used together code starts looking like confusing and difficult to follow.

Async/await was introduced in ES2017 to:

  • Improve readability

  • Make asynchronous code look synchronous

  • Simplify error handling

  • Reduce Promise chaining complexity

In simple words

Async/await is syntactic sugar over Promises.

That means it does not replace Promises internally -> it simply provides a cleaner syntax to work with them.

How Async Functions Work

An async function always returns a Promise.

Syntax :

async function greet() {
  return "Namaste Developer";
}

Even though we return a string, JavaScript automatically wraps it inside a Promise.

Equivalent Promise version

function greet() {
  return Promise.resolve("Namaste Developer!");
}

Understanding the Await Keyword

The await keyword pauses execution of an async function until the Promise resolves.

Here is an example,

function orderChai() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Chai is Ready");
    }, 2000);
  });
}

async function serveCustomer() {
  console.log("Making chai...");

  const result = await orderChai();

  console.log(result);
}

serveCustomer();

// OUTPUT
// Making chai...
// (2 second pause)
// Chai is Ready

Here is what happens:

  1. orderChai() returns a Promise

  2. await pauses execution

  3. JavaScript waits for the Promise to resolve

  4. Execution resumes

Error Handling with Async Code

One of the advantages of async/await is clean error handling. Instead of .catch(), we can use try/catch.

Here is an Example,

function paymentSuccessful() {
  return new Promise((resolve, reject) => {
    const success = false;

    setTimeout(() => {
      if (success) {
        resolve("Payment Successful");
      } else {
        reject("Payment Failed");
      }
    }, 2000);
  });
}

async function processPayment() {
  try {
    const message = await paymentSuccessful();

    console.log(message);

  } catch (error) {
    console.log("Error:", error);
  }
}

processPayment();

// OUTPUT
// Error: Payment Failed

Promise vs Async/Await Flow

Using Promises

getUser()
  .then((user) => {
    return getOrders(user);
  })
  .then((orders) => {
    return processOrders(orders);
  })
  .catch((err) => {
    console.log(err);
  });

Using Async/Await

async function handleOrders() {
  try {
    const user = await getUser();

    const orders = await getOrders(user);

    const result = await processOrders(orders);

    console.log(result);

  } catch (err) {
    console.log(err);
  }
}

Difference

Promises Async/Await
Uses .then() chaining Looks synchronous
Harder to read in complex flows Cleaner syntax
Nested chains possible Easier maintenance
Error handling split Centralized with try/catch

Important Things to Remember

  1. Await Only Works Inside Async Functions

    await fetchData();  // Wrong
    
    async function getData() { // Correct Way
        await fetchData();
    }
    
  2. Async Functions Always Return Promises

    async function test() {
        return 10;
    }
    
    // ACTUALLY RETURNS
    Promise { 10 } 
    
  3. Await Does Not Block Entire JavaScript. It only pauses the current async function and Other operations continue running normally.

When Should You Use Async/Await?

Use async/await when:

  • Fetching API data

  • Reading files Database operations

  • Delayed tasks

  • Sequential async operations

Examples :

  • Loading IPL scores from an API

  • Processing UPI payments

  • Fetching weather updates

  • Uploading Aadhaar documents

Limitations of Async/Await

Although async/await improves readability, it has some drawbacks:

  1. Sequential Execution Can Be Slower

    await task1();
    await task2();
    
    // Here, task2 waits for task1
    
    
    // Sometimes running tasks in parallel using Promise.all() is faster.
    const [user, posts] = await Promise.all([ getUser(), getPosts() ]); 
    
  2. Still Based on Promises
    Async/await is not a replacement for Promises. Under the hood, it still uses Promises internally.

Conclusion

If you’re learning modern JavaScript, mastering async/await is essential. You’ll use async programming everywhere.

And once you start using async/await, going back to long Promise chains feels like returning from 5G to 2G network.

Thanks for Reading.