Get "PHP 8 in a Nuthshell" (Soon includes PHP 8.4)
Amit Merchant

Amit Merchant

A blog on PHP, JavaScript, and more

Run multiple awaits in parallel using Promise.all()

Let’s talk about parallel promises today. So, in JavaScript, Promises provides a way of doing asynchronous programming in the language.

Essentially, Promises allows you to execute, composing and execute non-synchronous tasks such as consuming APIs.

In this article, we’ll be covering a primer to Promises and then take a look at how we can run promises in parallel.

A little intro to Promise and async/await

Let’s say if we want to consume an API through JavaScript’s fetch, here’s how we can do that.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json => console.log(json));

As you can tell, the fetch() method takes the path to the resource you want to fetch as its first argument and then returns a Promise that resolves to the Response to that request.

The response then can be handled further by chaining the then method as shown in the example.

If we take the example further and if we want to call another API based on the response.

For instance, if we want to retrieve all the Todos of a certain user, here’s how we can do it.

fetch('https://jsonplaceholder.typicode.com/users/1')
    .then(response => response.json())
    .then(json => {
        const userId = json.id;
        return fetch('https://jsonplaceholder.typicode.com/todos/${userId}');
    })
    .then(todoResponse => todoResponse.json())
    .then(todoJson => console.log(todoJson));

And if we want to rewrite this example using async/await, we can do it like so.

async function fetchUserTodos() {
    const user = await fetch('https://jsonplaceholder.typicode.com/users/1');

    const userTodos = await fetch('https://jsonplaceholder.typicode.com/todos/' + user.id);

    console.log(userTodos);
}

fetchUserTodos()
    .catch(e => {
        console.log('There has been a problem with your fetch operation: ' + e.message);
    });

As you can tell, in this example, the second API call is dependent on the first one because we’re using the user.id to fetch the second API. So, if the first API call takes 2 seconds to resolve the Promise, the second API call will be picked up after 2 seconds.

And if the second API call takes 3 seconds to complete, the entire operation would take 5 seconds (2 + 3) to complete.

This is fine since the APIs are depended on each other. But consider the scenario where the APIs are not dependent. In such a case, you might want to run all of them in parallel so that none of the API blocks one another.

Run non-dependent Promises concurrently

Let’s take the following example where we are fetching posts, photos, and todos of a user. Here, all the three resources can be fetched individually without being dependent on each other.

So, let’s check how would we normally fetch them using async/await, and then we’ll see, how we can fetch them parallelly.

async function fetchUserResources() {
    const posts = await fetch('https://jsonplaceholder.typicode.com/posts/1'); // Takes 2 seconds

    const photos = await fetch('https://jsonplaceholder.typicode.com/photos/1'); // Takes 2 seconds

    const todos = await fetch('https://jsonplaceholder.typicode.com/todos/1'); // Takes 2 seconds

    return {posts, photos, todos};
}

As you can tell, if all the three API calls take 2 seconds each to complete, the entire operation would take 6 seconds to finish.

I’m pretty sure you don’t want that to happen. So, how can we resolve it?

Using Promise.all() method

So, to run all these APIs in parallel, we can use Promise.all() like so.

async function fetchUserResources() {
    const [posts, photos, todos] = await Promise.all([
        fetch('https://jsonplaceholder.typicode.com/posts/1'),
        fetch('https://jsonplaceholder.typicode.com/photos/1'),
        fetch('https://jsonplaceholder.typicode.com/todos/1')
    ]);

    return {posts, photos, todos};
}

As you can tell, now we’re running all the three APIs in parallel through Promise.all() through a single await. And since all the APIs are running in parallel, the entire operation would only take 2 seconds to complete instead of 6 seconds as we saw in the previous example.

But the problem with this approach is, Promise.all() will be rejected entirely if any of the API is failed among the three. That’s when Promise.allSettled() would come into the picture.

Using Promise.allSettled() method

As I said, a much better approach here would be to use the Promise.allSettled() method that returns a Promise that resolves after all of the given promises have either been fulfilled or rejected, with an array of objects that each describes the outcome of each promise in form of a status and a value or a reason.

So, if we want to re-write the previous example using Promise.allSettled() and let’s say the “Todos” API would fail, here’s how we can do it.

async function fetchUserResources() {
    const [posts, photos, todos] = await Promise.allSettled([
        fetch('https://jsonplaceholder.typicode.com/posts/1'),
        fetch('https://jsonplaceholder.typicode.com/photos/1'),
        fetch('https://jsonplaceholder.typicode.com/todos/1')
    ]);

    console.log(posts);
    // {status: "fulfilled", value: {"holds_the_posts_object"}}
    
    console.log(photos);
    // {status: "fulfilled", value: {"holds_the_photos_object"}}

    console.log(todos);
    // {status: "rejected", reason: The error}

    return {posts, photos, todos};
}

As you can tell, even if one of the API calls given to Promise.allSettled() fails, the rest of the APIs will still be fulfilled while still having the rejected Promise with the reason why it was rejected.

Like this article?

Buy me a coffee

👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.

Comments?