Skip to main content

Command Palette

Search for a command to run...

Async/Await in JavaScript

Updated
8 min read
Async/Await in JavaScript

Why async/await was introduced

Introduced in ES2017, async/await is a modern syntax in JavaScript designed to simplify working with Promises. It allows you to write asynchronous code that looks and behaves like synchronous code, making it significantly more readable and easier to maintain.

Async/await was introduced to simplify asynchronous programming by allowing non-blocking, asynchronous code to be written and read like traditional synchronous code. It solves complex issues—such as "[callback hell]" and deeply nested .then() promise chains—making code more readable, maintainable, and easier to debug.

Key Components

  • async Keyword: Declares an asynchronous function. These functions always return a Promise. If the function returns a non-promise value, JavaScript automatically wraps it in a resolved Promise.

  • await Keyword: Can only be used inside an async function. It pauses the execution of the function until the Promise settles (either resolves or rejects). Once settled, it returns the resolved value or throws the rejected error.

  • Error Handling: Instead of using .catch() chains, you can use standard try...catch blocks within the async function to handle errors more cleanly.

Execution Flow

  1. When an async function is called, it executes synchronously until it reaches the first await expression.

  2. At the await, progress through that specific function is suspended, and control is yielded back to the caller while the promise settles.

  3. The JavaScript engine continues executing other tasks (like handling events or other scripts) in the meantime, ensuring the application remains responsive.

  4. Once the promise resolves, the async function resumes execution from where it left off.

Practical Benefits

  • Readability: Eliminates "callback hell" and long .then() chains, making the flow of logic easier to follow.

  • Parallel Execution: While await is sequential by default, you can initiate multiple promises simultaneously and then use Promise.all() to wait for all of them to resolve concurrently.

  • Maintainability: Debugging is easier because the call stack for errors is more representative of the code's logical flow.

How async functions work

JavaScript relies heavily on asynchronous programming to handle tasks like API calls, timers, and file operations without blocking execution. One of the cleanest ways to work with async code is using async and await.


🔹 What is an Async Function?

An async function is a function that always returns a Promise.

async function greet() {
  return "Hello";
}

👉 Even though it returns a string, JavaScript wraps it inside a Promise.


🔹 What is await?

The await keyword is used inside async functions to pause execution until a Promise is resolved.

async function getData() {
  let response = await fetch("https://api.example.com");
  let data = await response.json();
  console.log(data);
}

await makes async code look like synchronous code


🔹 How It Works Behind the Scenes

  • JavaScript is single-threaded

  • Async tasks are handled by:

    • Web APIs

    • Callback queue

    • Event loop

When await is used:

  1. Function pauses

  2. Other code continues

  3. Once Promise resolves → execution resumes


🔹 Example (Step-by-step)

async function example() {
  console.log("Start");

  await new Promise(resolve => setTimeout(resolve, 2000));

  console.log("End");
}

example();
console.log("Outside");

Output:

Start
Outside
End

👉 Shows non-blocking behavior


🔹 Error Handling in Async Functions

Use try...catch:

async function fetchData() {
  try {
    let res = await fetch("wrong-url");
    let data = await res.json();
  } catch (error) {
    console.log("Error:", error.message);
  }
}

🔹 Why Use Async/Await?

  • Cleaner and readable code

  • Easier than callbacks

  • Better error handling

  • Avoids “callback hell”

🔹 Important Points

  • async → makes function return Promise

  • await → pauses execution

  • Works only inside async functions

  • Makes code look synchronous but runs asynchronously

🔹 Conclusion

Async functions simplify asynchronous programming by allowing developers to write clean, readable, and maintainable code. By combining async and await, you can handle complex operations like API calls with ease.

Await keyword concept

The await keyword pauses the execution of an async function, waiting for a Promise (or similar task) to settle—either resolve or reject—before resuming. It enables asynchronous, non-blocking code to be written in a synchronous-looking style. When paused, it yields control back to the caller, allowing other tasks to run.

Key Concepts of await

  • Context: It can only be used inside a function marked as async in languages like JavaScript, Python, and C#.

  • Non-Blocking Behavior: await does not pause the entire application's execution thread, only the specific async function it is within.

  • Unwrapping Promises: It extracts the fulfilled value from a Promise, making it easy to assign the result directly to a variable (e.g., let result = await promise;).

  • Error Handling: If the promise is rejected, await throws the exception, which can be caught using traditional try/catch blocks.

Why Use await?
It improves code readability by avoiding complex Promise chaining (e.g., .then().catch()) and allows for sequential-looking code for asynchronous operations like fetching data, reading files, or database queries.

Error handling with async code

Asynchronous code makes applications fast and responsive—but it also introduces new ways for errors to occur. Handling these errors properly is essential to build reliable and user-friendly applications.

🔹 Why Error Handling Matters in Async Code

When working with:

  • API calls

  • Promises

  • Timers

Errors may not appear immediately. Without handling them, your app may:

  • Fail silently ❌

  • Crash unexpectedly ❌

🔹 1. Using try...catch with async/await

The most common and clean way to handle async errors:

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

try handles any error thrown during await

🔹 2. Handling Errors with Promises (.catch())

If you’re not using async/await, use .catch():

fetch("https://api.example.com")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(error => console.log("Error:", error));

Catches rejected promises

🔹 3. Throwing Errors in Async Functions

You can throw custom errors inside async functions:

async function login(password) {
  if (password.length < 6) {
    throw new Error("Password too short");
  }
  return "Success";
}

async function main() {
  try {
    await login("123");
  } catch (e) {
    console.log(e.message);
  }
}

🔹 4. Important Note

await only catches rejected promises, not logical mistakes

async function test() {
  try {
    let data = await Promise.reject("Failed");
  } catch (e) {
    console.log("Caught:", e);
  }
}

🔹 5. Using finally with Async Code

async function example() {
  try {
    await fetch("url");
  } catch (e) {
    console.log("Error");
  } finally {
    console.log("Always runs");
  }
}

Useful for cleanup tasks

🔹 Best Practices

  • Always wrap await in try...catch

  • Use meaningful error messages

  • Don’t ignore errors

  • Handle errors at the right level

🔹 Conclusion

Error handling in async code ensures your application remains stable and predictable. Whether you use try...catch or .catch(), the goal is to gracefully handle failures without breaking the app.


Comparison with promises

When working with asynchronous code in JavaScript, you’ll commonly use Promises or async/await. Both solve the same problem—but in different ways.


🔹 What are Promises?

A Promise represents a value that may be available now, later, or never.

fetch("https://api.example.com")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log(err));

Uses .then() and .catch() chaining


🔹 What is Async/Await?

async/await is a cleaner way to work with Promises.

async function getData() {
  try {
    let res = await fetch("https://api.example.com");
    let data = await res.json();
    console.log(data);
  } catch (err) {
    console.log(err);
  }
}

Makes async code look synchronous

🔹 Key Differences

Feature Promises Async/Await
Syntax .then().catch() async/await
Readability Less readable (chaining) More readable
Error Handling .catch() try...catch
Debugging Harder Easier
Flow Control Chaining Looks synchronous

🔹 Example Comparison

Using Promises:

fetch("url")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log(err));

Using Async/Await:

async function fetchData() {
  try {
    let res = await fetch("url");
    let data = await res.json();
    console.log(data);
  } catch (err) {
    console.log(err);
  }
}

🔹 When to Use What?

Use Promises when:

  • You need simple chaining

  • Working with existing .then() code

Use Async/Await when:

  • You want cleaner, readable code

  • Handling multiple async operations

  • Writing modern JavaScript

🔹 Important Note

async/await is built on top of Promises
It does not replace them—it simplifies them

🔹 Conclusion

Both Promises and async/await are powerful tools for handling asynchronous operations. However, async/await is generally preferred because it:

  • Improves readability

  • Simplifies error handling

  • Makes code easier to