Don’t ever use Promise.all()

Imagine that you have two asynchronous calls like so:

async function getData() {
  const users = await fetchUsers();
  const categories = await fetchCategories();
}

While the code above will get the job done, you are missing a major opportunity for huge performance gains. These two API calls are run sequentially, meaning that for “fetchCategories” to run, we will wait until “fetchUsers” is completed.

The obvious change here will be to change our code to use Promise.all() so that these two calls run concurrently.

async function getData() {
   const [users, categories] = await Promise.all(
      fetchUsers(),
      fetchCategories(),
   )
}

The Problem

Errors for each of these two API calls are not handled. The (obvious) next step you would think would be to add a try-catch around it, something like the following:

async function getData() {
   try {
      const [users, categories] = await Promise.all(
         fetchUsers(),
         fetchCategories(),
      )
   } catch (err) {
      reportToLogger(err);
   }
}

That would lead you to believe that any error thrown inside that try-catch would be handled, which is not the case.

The issue is that if fetchUsers throws an error, that error would actually be handled. Any other errors thrown after that though, will not. This means that if fetchCategories throws an exception too for whatever reason, the catch block will not be executed and an “Unhandled Promise Rejection” error will be thrown to the user.

The Solution

We can solve this by using Promise.allSettled().

async function getData() {
   const [userResult, categoryResult] = await Promise.allSettled(
      fetchUsers(),
      fetchCategories(),
   )
}

The Promise.allSettled method returns a Result Object back. The returned value (or error) is wrapped inside an object which we can use to check if the request was actually fulfilled or rejected. No errors are ever thrown so our code will be fluent and non-nested.

async function getData() {
   const [userResult, categoryResult] = await Promise.allSettled(
      fetchUsers(),
      fetchCategories(),
   )

   if (userResult.status === 'rejected') {
     const error = userResult.reason
     // handle error
   } else {
     const users = userResult.value
   }

   if (categoryResult.status === 'rejected') {
     const error = categoryResult.reason
     // handle error
   }
}

Additional Resources

  1. Effective Promise Concurrency in JavaScript (builder.io)
  2. Promise.all() – JavaScript | MDN (mozilla.org)
  3. How does Promise.all() method differs from Promise.allSettled() method in JavaScript ? – GeeksforGeeks

2 thoughts on “Don’t ever use Promise.all()

  1. Sam Apostel

    You can also opt to assign the promises to variables and await them after all the requests are initiated. This gives you some more control with error handling. For example: you don’t have to wait for the categories if the user isn’t set.

    const userPromise = fetchUser();
    const categoriesPromise = fetchCategories();
    const userResult = await userPromise;
    const categoriesResult = await categoriesPromise;

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *