Skip to main content

Command Palette

Search for a command to run...

Async/Await in JavaScript: Writing Cleaner Asynchronous Code

Updated
3 min read

If you've been writing Javascript for a while, you know the struggle of managing operations that take time like fetching data from API or reading a file. We started with Callbacks (and quickly ended up in "Callback Hell"), moved to Promises, and finally arrived at the standard: Async/Await.

Introduced in ES2017, async/await isn't a new way of doing things, it's a better way of writing things.

Why Async/Await?

Before async/await , we used Promises. While Promises solved nesting issues of callbacks, they still relied on .then() and .catch() chains. For complex logic, these chains could become hard to follow.

async/await is often called syntactic sugar because it's built on top of Promises. It doesn't change how JavaScript works under the hood; it just allows us to write aynchronous code that looks and behaves like synchronous code.

How Async Functions Work

To use this feature, you start by declaring a function with the async keyword. This simple addition ensures the function always returns a promise.

async function greet() {
  return "Hello, World!";
}

// Even though we returned a string, it's wrapped in a Promise
greet().then(message => console.log(message));

The Power of the await Keyword

The await keyword can only be used inside an async function. It tells JavaScript: "Pause execution of this function until this promise settles (resolves or rejects)."

While the function pauses, the rest of your application keeps running, meaning your UI won't freeze.

Comparison: Promises vs Async/Await

Let's look at how much cleaner our code becomes when fetching user data.

Using Standard Promises:

function getUserData() {
  fetch('https://api.example.com/user')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error("Error:", error));
}

Using Async/Await:

async function getUserData() {
  try {
    const response = await fetch('https://api.example.com/user');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Error:", error);
  }
}

Why the second one is better:

  • Readability: It reads like a book. Step 1: get response. Step 2: get data.

  • Debugging: Stack traces are easier to read because the code looks linear.

  • Variables: You don't have to keep passing data down a chain of .then() blocks.

Handling Errors with Grace

One of the biggest wins for async/await is that we can use standard try...catch blocks. This is the same pattern we use for synchronous code, making error handling much more intuitive.

async function safeFetch() {
  try {
    const result = await someRiskyBusiness();
    return result;
  } catch (err) {
    console.log("Something went wrong:", err.message);
  }
}

Key Takeaways

  • async makes a function return a Promise.

  • await pauses the function until the Promise resolves.

  • It eliminates "Promise chaining" and makes code much more readable.

  • Use try/catch for clean error management.

8 views