JavaScript Async/Await

JavaScript async and await make asynchronous programming easier to read and understand. They allow you to write code that looks similar to synchronous code while working with Promises.

These keywords are commonly used when fetching data from APIs, loading information from servers, processing asynchronous tasks, and handling operations that may take time to complete.

The async keyword declares an asynchronous function, while await pauses the progress of that function until a Promise settles. Error handling with try...catch helps you manage failures without making your code difficult to understand.

In this tutorial, you will learn how async and await work, how to create async functions, how to handle errors, and how to use try...catch in practical JavaScript examples.

What Is Async/Await in JavaScript?

Async/await is JavaScript syntax for working with Promises. It provides a convenient way to write asynchronous code without chaining multiple .then() calls.

Before async/await, developers commonly used Promise chaining to handle asynchronous operations. Async/await allows those operations to be written in a more sequential style.

Example Without Async/Await

function getData() {
  return Promise.resolve("Data loaded successfully");
}

getData()
  .then(function (result) {
    console.log(result);
  })
  .catch(function (error) {
    console.log(error);
  });

Output:

Data loaded successfully

The .then() method handles the fulfilled Promise, while .catch() handles a rejection.

Example Using Async/Await

function getData() {
  return Promise.resolve("Data loaded successfully");
}

async function displayData() {
  const result = await getData();

  console.log(result);
}

displayData();

Output:

Data loaded successfully

The await keyword allows displayData() to wait for the Promise’s outcome before assigning the fulfillment value to result.

The function pauses its own progress at await, but it does not block the entire JavaScript thread while the Promise is pending.

The async Keyword in JavaScript

The async keyword is used before a function declaration or function expression to define an asynchronous function.

An async function always returns a Promise. If the function returns a regular value, JavaScript fulfills the returned Promise with that value. If the function throws an error, the returned Promise is rejected.

Syntax

async function functionName() {
  // Asynchronous code
}

Example of an Async Function

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

greet().then(function (message) {
  console.log(message);
});

Output:

Hello, JavaScript!

Although greet() returns a string inside the function, calling it produces a Promise fulfilled with that string.

You can think of this example as similar to returning Promise.resolve("Hello, JavaScript!").

Returning a Value from an Async Function

async function calculate() {
  return 100;
}

calculate().then(function (result) {
  console.log(result);
});

Output:

100

The value 100 becomes the fulfillment value of the Promise returned by calculate().

Returning a Promise from an Async Function

An async function can also return another Promise.

async function getUser() {
  return Promise.resolve({
    name: "Rahul",
    age: 25
  });
}

getUser().then(function (user) {
  console.log(user.name);
});

Output:

Rahul

The Promise returned by getUser() adopts the outcome of the Promise returned inside the function.

The await Keyword in JavaScript

The await keyword is used to wait for a Promise’s outcome inside an async function. When the Promise fulfills, the await expression evaluates to its fulfillment value.

If the Promise rejects, the await expression throws the rejection reason, allowing you to handle the error with try...catch.

In modern JavaScript, await can also be used at the top level of supported JavaScript modules.

Syntax

const result = await promise;

The result variable receives the fulfillment value when the Promise fulfills.

Example of await

function getMessage() {
  return Promise.resolve("Welcome to Web4Learners!");
}

async function displayMessage() {
  const message = await getMessage();

  console.log(message);
}

displayMessage();

Output:

Welcome to Web4Learners!

The getMessage() function returns a Promise. The await keyword waits for its outcome, and the resulting string is assigned to message.

Using await with a Timer

function delay() {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve("Task completed");
    }, 2000);
  });
}

async function runTask() {
  console.log("Task started");

  const result = await delay();

  console.log(result);
}

runTask();

console.log("Other code continues");

Output:

Task started
Other code continues
Task completed

The runTask() function pauses at await delay() until the Promise fulfills. Meanwhile, JavaScript continues executing the remaining synchronous code.

After approximately two seconds, the delayed operation fulfills the Promise and runTask() continues.

The delay is approximate, and the callback may run later if the runtime is busy.

Async Functions in JavaScript

An async function is a function declared with the async keyword. It can use await to work with Promises and always returns a Promise.

Async functions can be written as declarations, expressions, arrow functions, and object methods.

Async Function Declaration

async function getData() {
  return "Data received";
}

getData().then(function (data) {
  console.log(data);
});

Output:

Data received

Async Function Expression

const getData = async function () {
  return "Data received";
};

getData().then(function (data) {
  console.log(data);
});

Output:

Data received

Here, an async function expression is assigned to the getData variable.

Async Arrow Function

const getData = async () => {
  return "Data received";
};

getData().then(function (data) {
  console.log(data);
});

Output:

Data received

The async keyword appears before the arrow function’s parameter list.

Async Function with Parameters

async function addNumbers(a, b) {
  return a + b;
}

addNumbers(10, 20).then(function (result) {
  console.log(result);
});

Output:

30

The function calculates the sum and returns it through a fulfilled Promise.

Error Handling in Async/Await

Asynchronous operations can fail for many reasons. For example, a network request may fail, a server may return an error, or a function may encounter invalid data.

When using async/await, a rejected Promise causes the await expression to throw its rejection reason. You can handle this with try...catch, or handle the rejected Promise returned by the async function with .catch().

Example of an Async Function Error

async function getData() {
  throw new Error("Unable to load data");
}

getData().catch(function (error) {
  console.log(error.message);
});

Output:

Unable to load data

The throw statement causes the async function’s returned Promise to reject.

The .catch() method handles the rejection and displays the error message.

Handling a Rejected Promise with await

function getData() {
  return Promise.reject(new Error("Request failed"));
}

async function displayData() {
  const result = await getData();

  console.log(result);
}

displayData().catch(function (error) {
  console.log(error.message);
});

Output:

Request failed

Because getData() returns a rejected Promise, the await expression throws an error. The displayData() function therefore returns a rejected Promise, which is handled by the caller’s .catch() method.

This approach is useful when you want to keep the async function focused on its main task and handle failures at the point where the function is called.

The try…catch Statement in Async/Await

The try...catch statement allows you to handle errors without stopping the entire program unexpectedly.

The try block contains code that might fail. If an exception is thrown inside it, execution moves to the catch block.

This includes errors thrown by an await expression when the awaited Promise rejects.

Syntax

try {
  // Code that may throw an error
} catch (error) {
  // Handle the error
}

Example of try…catch

async function getData() {
  try {
    const result = await Promise.reject(
      new Error("Something went wrong")
    );

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

getData();

Output:

Error: Something went wrong

The rejected Promise causes the await expression to throw. The catch block receives the error and displays its message.

Because the error is handled inside the function, this example does not leave the rejection unhandled.

Using try…catch with a Successful Operation

async function getUser() {
  try {
    const user = await Promise.resolve({
      name: "Ayesha",
      age: 24
    });

    console.log("Name:", user.name);
    console.log("Age:", user.age);
  } catch (error) {
    console.log("Error:", error.message);
  }
}

getUser();

Output:

Name: Ayesha
Age: 24

Because the Promise fulfills successfully, execution continues inside the try block and the catch block is skipped.

Using finally for Cleanup

You can combine try...catch with finally when some code must run regardless of success or failure.

async function loadData() {
  try {
    console.log("Loading data");

    const result = await Promise.resolve("Data loaded");

    console.log(result);
  } catch (error) {
    console.log("Error:", error.message);
  } finally {
    console.log("Loading finished");
  }
}

loadData();

Output:

Loading data
Data loaded
Loading finished

The finally block runs after the try or catch block. It is useful for hiding loading indicators, re-enabling buttons, and performing cleanup.

Practical Example: Fetching Data with Async/Await

One of the most common uses of async/await is retrieving information from an API.

The browser’s fetch() function returns a Promise that fulfills with a Response when a response is received. You should also check response.ok, because HTTP error statuses such as 404 or 500 do not automatically cause fetch() to reject.

Example of Fetching API Data

async function getUser() {
  try {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/users/1"
    );

    if (!response.ok) {
      throw new Error("Failed to fetch user");
    }

    const user = await response.json();

    console.log("Name:", user.name);
    console.log("Email:", user.email);
  } catch (error) {
    console.log("Error:", error.message);
  }
}

getUser();

When the request succeeds, the code displays the user’s name and email from the API response. If the network request fails, the response status is unsuccessful, or JSON parsing fails, the catch block handles the resulting error.

The request requires an internet connection and a working API endpoint. The exact response should be checked when running the example.

Understanding the Example

  • fetch() sends the network request and returns a Promise.
  • await fetch() waits for the response without blocking the entire JavaScript thread.
  • response.ok checks whether the HTTP status is in the successful range.
  • response.json() returns a Promise that parses the response body as JSON.
  • try...catch handles failures from the request, status check, or JSON parsing.

This pattern is commonly used in applications that display user profiles, product information, weather data, and other information retrieved from APIs.

Difference Between Promises and Async/Await

Async/await is built on Promises. It provides another way to write code that works with them.

FeaturePromise ChainingAsync/Await
Main syntax.then() and .catch()async, await, and optionally try...catch
ReadabilityCan become lengthy with many stepsOften resembles sequential code
Error handlingUsually handled with .catch()Can use try...catch or .catch() on the returned Promise
Return value.then() returns a new PromiseAn async function always returns a Promise
Best useChaining operations and composing Promise resultsWriting sequential asynchronous workflows clearly

Both approaches are valid. Choose the style that makes the logic easiest to read and maintain.

Common Mistakes When Using Async/Await

Forgetting to Use await

Calling an async function without await gives you a Promise rather than its resolved value.

async function getNumber() {
  return 10;
}

const result = getNumber();

console.log(result);

The console displays a Promise object, not simply the number 10.

To obtain the value inside another async function, use:

const result = await getNumber();

console.log(result);

Forgetting to Handle Errors

If an awaited Promise rejects and the error is not caught inside the async function, the async function’s returned Promise rejects too. Handle the error using try...catch or .catch() at the appropriate level.

Assuming await Blocks All JavaScript

await pauses the current async function, not the entire JavaScript thread. Other code can continue running while the awaited Promise is pending.

Using await Sequentially When Tasks Are Independent

If two asynchronous operations do not depend on each other’s results, awaiting them one after another can unnecessarily increase total waiting time.

For independent operations, consider starting them together with Promise.all().

async function loadData() {
  const [users, products] = await Promise.all([
    fetch("/api/users"),
    fetch("/api/products")
  ]);

  console.log(users, products);
}

This example starts both requests together. In production code, check each response’s ok property and process the response bodies as needed.

Best Practices for Async/Await

  • Use async and await when they make asynchronous logic easier to understand.
  • Handle expected failures with try...catch or an appropriate .catch() handler.
  • Check HTTP response status when using fetch().
  • Avoid awaiting independent tasks sequentially when they can safely run together.
  • Use finally for cleanup that should happen regardless of success or failure.
  • Remember that async functions always return Promises.
  • Do not ignore rejected Promises when their failures matter to the application.
  • Keep error messages useful without exposing sensitive information.

Frequently Asked Questions

What does async mean in JavaScript?

The async keyword declares an asynchronous function. The function always returns a Promise, even when it returns a regular value.

What does await do in JavaScript?

The await keyword waits for a Promise’s outcome inside an async function or supported top-level module context. It evaluates to the fulfillment value or throws the rejection reason.

Can await be used without async?

In modern JavaScript, await can be used inside async functions and at the top level of supported ECMAScript modules. In ordinary scripts and outside async functions, it generally cannot be used as a standalone statement.

What is the purpose of try…catch in async functions?

try...catch handles exceptions and rejected Promises encountered through await. It allows an application to display an error message or take another appropriate action when an operation fails.

Does an async function always return a Promise?

Yes. A regular returned value becomes the fulfillment value of the returned Promise, while an uncaught exception causes that Promise to reject.

What is the difference between async/await and then/catch?

Both work with Promises. .then() and .catch() use Promise handlers, while async/await provides a sequential style that can be easier to read when multiple asynchronous steps depend on each other.

Is async/await blocking?

No. await pauses the current async function while its Promise is pending, but it does not block the entire JavaScript thread.

Conclusion

JavaScript async/await makes asynchronous programming easier to read, write, and maintain. The async keyword declares a function that returns a Promise, while await allows the function to continue after a Promise settles.

Error handling with try...catch helps you manage failed operations, and finally allows cleanup to run after success or failure. These techniques are essential for working with APIs, network requests, and other asynchronous tasks in modern JavaScript applications.