Call async/await Functions in Parallel: Using Promise.all, Promise.allSettled, and Promise.race

Asynchronous programming is a vital part of JavaScript in the sense that it lets us execute multiple tasks at once no matter how complex each task may be. With the introduction of async/await, asynchronous programming has become easier than ever before! However, in some cases, we may need to execute multiple independent asynchronous tasks at a time to improve the overall performance of a program. Node.js provides various methods to execute async/await functions in parallel. In this article, we’ll look at some of these methods with some examples.

Understanding async/await Functions

The async/await is used in JavaScript to enable asynchronous programming while making the code promise-based.

The async keyword is used to define an asynchronous function. Using this keyword signifies that the function always returns a promise.

The await keyword is used inside an async function to halt the execution of the function until the promise is resolved and a result is obtained.

Example:

Here, we’ll write an async function that makes an HTTP request to an API and retrieves the first entry and logs it onto our console if the request is made successfully or logs an error message if the request is not made successfully.

async function sendHttpRequest() {
  try {
    //fetching the first entry from the API
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

sendHttpRequest();

We define an asynchronous function sendHttpRequest() using the async keyword. Inside this function we use a try…catch block which checks if the request made to the API is successful. We use the fetch() method to make a request to an API that returns a promise. The returned promise is stored in a constant response.

The await keyword is used to pause the execution of the asynchronous function until the promise returned by response.json() is resolved. The json() method returns the promise as a JavaScript object and is stored in constant data.

The catch statement logs an error on the console if the promise is unresolved.

Output:

Response received on fetching URL using async/await
Response received on fetching URL using async/await

Methods for Calling async/await Functions in Parallel

Node.js provides several methods to achieve parallel execution of async/await functions. Some of these methods are as follows:

  • Promise.all()
  • Promise.allSettled()
  • Promise.race()

1. Using Promise.all()

The Promise.all() method takes an array of promises as an input and returns a single promise if all of the promises in the array are fulfilled. However, Promise.all() is rejected if even one of the input promises is not fulfilled. This method is typically used when we have multiple related asynchronous functions that need to be executed concurrently.

Example:

Let’s look at an example where we fetch multiple URLs using Promise.all().

async function fetchURLs(urls) {
    try {
      const promises = urls.map(url => fetch(url)); 
      const responses = await Promise.all(promises); 
      const data = await Promise.all(responses.map(response => response.json())); 
      console.log(data);
    } catch (error) {
      console.error(error);
    }
  }
  
const urls = ['https://jsonplaceholder.typicode.com/posts/1', 'https://jsonplaceholder.typicode.com/posts/2', 'https://jsonplaceholder.typicode.com/posts/3'];
  
fetchURLs(urls);

Here, we have an asynchronous function fetchURLs() that takes an array of URLs (referred to by the constant urls) as input. Inside the function we have a try…catch block which logs data onto the console if all the promises are resolved successfully or throws an error if a promise is not resolved.

The map() function is used to iterate through the array of URLs and fetch each URL (referred to by the constant url). The promise returned after fetch() is stored in a constant called promises. We then use await to pause the execution of the function until all promises have been resolved by Promise.all(). The resolved promises are stored in constant responses.

We then use await with Promise.all() to extract JSON data from each of the responses. The map() function is used once again to iterate through each of the responses. The data is then logged onto the console once all the promises have been resolved.

Output:

Response of Multiple Fetch Requests to an API
Response received on fetching multiple URLs concurrently using Promise.all()

2. Using Promise.allSettled()

Promise.allSettled() is another method used to execute multiple asynchronous functions parallelly. It is similar to Promise.all() except for the fact that Promise.allSettled() doesn’t reject if any of the promises are not fulfilled but rather waits for all of the other promises to fulfil.

Promise.allSettled() returns a promise along with an array containing the status of whether each of the input promises has been fulfilled or rejected along with it’s value.

Example:

Let’s take the same example used above but use Promise.allSettled() to fetch multiple URLs.

async function fetchURLs(urls) {
    try {
      const promises = urls.map(url => fetch(url)); 
      const responses = await Promise.allSettled(promises); 
      const fulfilledResponses = responses.filter(response => response.status === 'fulfilled');
      const data = await Promise.allSettled(fulfilledResponses.map(response => response.value.json())); 
      console.log(data);
    } catch (error) {
      console.error(error);
    }
  }
  
 const urls = ['https://jsonplaceholder.typicode.com/posts/1', 'https://jsonplaceholder.typicode.com/posts/2', 'https://jsonplaceholder.typicode.com/posts/3'];
  
fetchURLs(urls);

Here we have an asynchronous function fetchURls() that takes in an array of URLs as input just like the previous example. We then map through the array of URLs and use fetch(), which returns a promise. Then we use await and Promise.allSettled() to pause the execution of the program until the promises are resolved.

As the Promise.allSettled() method returns the status of each of the promises, we can use the filter() method on the responses to check the status of each of the responses and filter out the ones having fulfilled status. These promises are stored in an array fulfilledResponses.

Now, await is used with Promise.allSettled() to extract JSON data by mapping through each of the fulfilled responses. The data containing the value along with the status of each response is logged onto the console.

Output:

Response of Multiple Fetch Requests to an API using Promise.allSettled
Response received on fetching multiple URLs concurrently using Promise.allSettled()

3. Using Promise.race()

Promise.race() is also a method used for concurrently resolving multiple promises which takes an array of promises as its input. It returns a new promise as soon as any promise in the array is either resolved or rejected. If the first promise is resolved then the promise returned by Promise.race() has the value of the resolved promise. If the first promise is rejected the resultant promise returned by Promise.all() is rejected.

Example:

Here, we’ll look at an example where we have two promises promiseA and promiseB which have a delay set on them using the in-built setTimeout() function. Promise.race() settles (resolves or rejects) as soon as either promiseA or promiseB is settled and returns the value of the promise which is settled first (having the lesser delay).

1. promiseA has lesser delay than promiseB

const promiseA = new Promise(resolve => setTimeout(() => resolve('A'), 2000));
const promiseB = new Promise(resolve => setTimeout(() => resolve('B'), 5000));

Promise.race([promiseA, promiseB])
  .then(result => console.log('result:', result))
  .catch(error => console.error(error));

Promise.race() settles as soon as promiseA is resolved first and then() returns the value of the promise. If promiseA is rejected the catch() statement throws an error.

Output:

Promise returned when Timeout value of A is lesser
Promise returned when Timeout value of A is lesser

2. promiseB has lesser delay than promiseA

const promiseA = new Promise(resolve => setTimeout(() => resolve('A'), 5000));
const promiseB = new Promise(resolve => setTimeout(() => resolve('B'), 2000));

Promise.race([promiseA, promiseB])
  .then(result => console.log('result:', result))
  .catch(error => console.error(error));

Since promiseB is resolved first, the resultant promise returned by Promise.race() has the value of promiseB.

Output:

Promise returned when Timeout value of B is lesser
Promise returned when Timeout value of B is lesser

3. First promise is rejected

const promiseA = new Promise(resolve => setTimeout(() => resolve('A'), 5000));
const promiseB = new Promise(reject => setTimeout(() => reject(new Error('B')), 3000));

Promise.race([promiseA, promiseB])
  .then(result => console.log('result:', result))
  .catch(error => console.error(error));

Here, the first promise settled is promiseB however, it has been rejected. So, the promise returned by Promise.race() is rejected and throws an error with the value B.

Output:

Promise returned when the first promise is rejected
Promise returned when the first promise is rejected

Conclusion

Parallel execution of async/await can greatly improve the performance of our program by giving us the ability to carry out multiple asynchronous tasks concurrently. In this article, we have discussed three ways of achieving parallel execution using Promise.all(), Promise.allSettled() and Promise.race() along with suitable examples. By keeping these techniques in mind we will be able to achieve greater optimization in our programs.

Reference

https://stackoverflow.com/questions/35612428/call-async-await-functions-in-parallel


Nandana Pradosh
Nandana Pradosh
Articles: 27