Mastering Async Await in JavaScript for Asynchronous Programming
Tags: webdev, javascript, async, frontend
Published: 2024-07-02 16:19:04

---
canonical_url: https://antondevtips.com/blog/mastering-async-await-in-javascript-for-asynchronous-programming
---

## Introduction

Asynchronous programming is a must-have in modern JavaScript development, allowing developers to perform non-blocking operations, such as fetching data from a server, reading files, or executing time-consuming operations.
**ES2017** introduced async functions and the `await` keyword that are a complete game changer in asynchronous development.
This blog post is a guide to using **async/await** to handle asynchronous tasks in an elegant way.

> **_On my website: [antondevtips.com](https://antondevtips.com/blog/mastering-async-await-in-javascript-for-asynchronous-programming?utm_source=devto&utm_medium=social&utm_campaign=02_07_24) I already have JavaScript blog posts._**
> **_[Subscribe](https://antondevtips.com/#subscribe) as more are coming._**

## How To Use Async/Await

`async/await` statements allow developers to write asynchronous code that looks and behaves like a synchronous code:

```js
async function fetchDataAsync() {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  const data = await response.json();
  return data;
}

const data = await fetchDataAsync();
console.log(data);
```

Here an `async` function returns a `Promise<T>`, that holds a data received from the API call.
By using `await` keyword we get this data as a promise result.

After the line `const data = await fetchDataAsync();` we can simply write more code as if all operations were executed synchronously.

`async/await` offer an elegant way for executing asynchronous operations represented by JavaScript Promises.

To learn more about promises [read my blog post](https://antondevtips.com/blog/understanding-javascript-promises-a-guide-to-asynchronous-programming).

`await` keyword is only allowed to be used in the `async` functions.

```js
function test() {
  const data = await fetchDataAsync(); // Syntax error
}

async function test() {
  const data = await fetchDataAsync(); // Now ok
}
```

## Async/Await in Top-Level Statements

With the introduction of ECMAScript 2022, JavaScript supports top-level `await` statements in modules.
This allows you to use `await` outside of async functions within modules, simplifying the initialization of resources.

```js
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
console.log(data);
```

Outside modules or in older versions of web browsers, you can use the following trick with anonymous async function to use `await` in top-level statements:

```js
(async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  const data = await response.json();
  console.log(data);
})();
```

## Async Methods as Class Members

You can encapsulate asynchronous logic within objects by defining async methods in JS classes.
It is a good practise to add `Async` suffix when naming asynchronous functions.

```js
class PostService {
  async getPostsAsync() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const data = await response.json();
    return data;
  }
}

const postService = new PostService();
const data = await postService.getPostsAsync();
console.log(data);
```

## Error Handing When Using Async/Await

Error handling when using `async/await` is straightforward by using `try/catch` statement:

```js
async function fetchDataAsync() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("Failed to fetch data: ", error);
  }
}
```

When a `reject` method is called while awaiting a promise - an exception is thrown, that can be handled in the `catch` block:

```js
function testPromise() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("Test Error"));
    }, 1000);
  });
}

try {
  const response = await testPromise();
} catch (error) {
  console.error("Failed to get result: ", error);
}
```

## Using Promise Utility Methods With Async/Await

Promise class has few utility static methods for asynchronous programming:
- Promise.all
- Promise.any
- Promise.race
- Promise.allSettled

### Promise.all

You can use `Promise.all` function to wait for multiple promises to resolve.
This function takes an array of promises and returns a new promise that resolves when all of the promises have resolved, or rejects if any promise is rejected.

This method is particularly useful when you have multiple asynchronous operations that can be executed in parallel.
Let's explore an example, where we fetch posts, comments and todos using `Promise.all`:

```js
async function fetchMultipleResourcesAsync() {
  const urls = [
    "https://jsonplaceholder.typicode.com/posts",
    "https://jsonplaceholder.typicode.com/comments",
    "https://jsonplaceholder.typicode.com/todos"
  ];

  try {
    const promises = urls.map(url => fetch(url));
    const responses = await Promise.all(promises);
    
    const data = await Promise.all(responses.map(res => res.json()));
    return data;
  } catch (error) {
    console.error("Error fetching one or more resources:", error);
  }
  
  return null;
}

const data = await fetchMultipleResourcesAsync();
console.log("Posts, comments, todos:", data);
```

It will be more efficient to fetch this data in parallel than fetching posts, comments and todos one by one.

### Promise.any

You can use `Promise.any` function to wait for one of multiple promises to resolve.
This function takes an array of promises and returns a single promise that resolves when the first of the promises is resolved.
If all the promises are rejected, then the returned promise is rejected with an AggregateError, an exception type that groups together individual errors.

```js
async function fetchFirstResourceAsync() {
  const urls = [
    "https://jsonplaceholder.typicode.com/posts",
    "https://jsonplaceholder.typicode.com/comments",
    "https://jsonplaceholder.typicode.com/todos"
  ];

  try {
    const promises = urls.map(url => fetch(url));
    const firstResponse = await Promise.any(promises);
    
    const data = await firstResponse.json();
    return data;
  } catch (error) {
    console.error("All requests failed:", error);
  }
  
  return null;
}

const data = await fetchFirstResourceAsync();
console.log("First available data:", data);
```


### Promise.race
`Promise.race` is similar to `Promise.any`, but it completes as soon as one of the promises is either resolved or rejected.
This method is useful for timeout patterns when you need to cancel request after a certain time.

```js
async function fetchDataWithTimeoutAsync() {
  const fetchPromise = fetch("https://jsonplaceholder.typicode.com/comments");
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Request timed out")), 5000));

  try {
    const response = await Promise.race([fetchPromise, timeoutPromise]);
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("Failed to fetch or timeout reached:", error);
  }
  
  return null;
}

const data = await fetchDataWithTimeoutAsync();
console.log("Comments received:", data);
```

## Promise.allSettled

You can use `Promise.allSettled` function to wait for all the promises to complete, regardless of whether they resolve or reject.
It returns a promise that resolves after all the given promises have either resolved or rejected.
This promise contains an array of objects where each describes the result of each promise.

```js
async function fetchMultipleResourcesAsync() {
  const urls = [
    "https://jsonplaceholder.typicode.com/posts",
    "https://jsonplaceholder.typicode.com/comments",
    "https://jsonplaceholder.typicode.com/todos"
  ];

  try {
    const promises = urls.map(url => fetch(url));
    
    const results = await Promise.allSettled(promises);
    
    const data = results.map((result, index) => {
      if (result.status === "fulfilled") {
        console.log(`Promise ${index} fulfilled with data:`);
        return result.value;  // Collecting fulfilled results
      } else {
        console.error(`Promise ${index} rejected with reason:`, result.reason);
        return null;  // You might want to return null or a default object
      }
    });

    return data;
    
  } catch (error) {
    console.error("Error fetching one or more resources:", error);
  }
  
  return null;
}

const data = await fetchMultipleResourcesAsync();
console.log("Posts, comments, todos:", data);
```


## Awaiting Thenable Objects

In JavaScript, a **thenable** is an object or function that defines a `then` method.
This method behaves similarly to the `then` method found in native promises.
That way `async/await` can handle these objects just like regular promises.

For example:

```js
class Thenable {
  then(resolve, reject) {
    setTimeout(() => resolve("Task completed"), 1000);
  }
}

const result = await new Thenable();
console.log(result);
```

Thenables objects can be useful for integrating with systems that don't use native promises but have promise-like behavior.
However, thenable objects can introduce confusion to the source code as their behavior is not straightforward.

Native promises are preferable due to their comprehensive feature set and better integration with the JavaScript ecosystem.

> **_On my website: [antondevtips.com](https://antondevtips.com/blog/mastering-async-await-in-javascript-for-asynchronous-programming?utm_source=devto&utm_medium=social&utm_campaign=02_07_24) I already have JavaScript blog posts._**
> **_[Subscribe](https://antondevtips.com/#subscribe) as more are coming._**