โ€” ยท 3 min read

Promise.all vs. Promise.allSettled

When it comes to handling multiple Promises concurrently, most developers just default to Promise.all. However, Promise.allSettled is a better alternative that provides more robust error handling.

Understanding Promise.all

Promise.all is a built-in method that takes an iterable (such as an array of Promises) and returns a new Promise. This new Promise resolves with an array of resolved values if all Promises are resolved successfully. However, if any of the Promises is rejected, the entire operation fails, and the Promise returned by Promise.all is rejected immediately, and no other Promises will be executed.

Let's say we have two asynchronous functions, fetchData1 and fetchData2, which return Promises that resolve with data fetched from the server.

const fetchData1 = (): Promise<string> => {
  return new Promise((resolve) => {
    // Simulating server delay
    setTimeout(() => resolve("Data from Server 1"), 2000);
  });
};
 
const fetchData2 = (): Promise<string> => {
  return new Promise((resolve) => {
    // Simulating server delay
    setTimeout(() => resolve("Data from Server 2"), 1500);
  });
};
 
const fetchData = async () => {
  try {
    const [data1, data2] = await Promise.all([fetchData1(), fetchData2()]);
    console.log(data1, data2);
  } catch (error) {
    console.error("Error occurred:", error);
  }
};

If either of the Promises returned by fetchData1 or fetchData2 is rejected, the entire operation will fail, and the catch block will be executed. Even though the other Promise is resolved successfully, we won't be able to access the data.

Promise.allSettled to the rescue

Promise.allSettled, introduced in ES2020 and supported in TypeScript, also takes an iterable of Promises but behaves differently from Promise.all. Instead of failing the entire operation when one Promise is rejected, Promise.allSettled waits for all Promises to either resolve or reject. The resulting Promise resolves with an array of objects, each representing the outcome of each Promise.

Each outcome object contains two properties:

  • status - Either "fulfilled" or "rejected"
  • value - The value if the Promise is fulfilled, or the reason if the Promise is rejected

Using the same fetchData1 and fetchData2 functions:

const fetchData = async () => {
  try {
    const [data1, data2] = await Promise.allSettled([
      fetchData1(),
      fetchData2(),
    ]);
    console.log(data1, data2);
  } catch (error) {
    console.error("Error occurred:", error);
  }
};

If fetchData1 is rejected, the resulting array will be:

[
  {
    status: "rejected",
    reason: "Error occurred: Error: Request failed with status code 404",
  },
  {
    status: "fulfilled",
    value: "Data from Server 2",
  },
];

Conclusion

When working with multiple Promises concurrently in TypeScript, choosing the right method is crucial for ensuring smooth asynchronous operations and robust error handling. While both Promise.all and Promise.allSettled have their use cases, Promise.allSettled stands out with its better error handling and easy-to-understand outcomes.

If you have an edge case where you need to abort the operation when one of the Promises is rejected, you can use Promise.all with a catch block to handle the error.

Remember, embracing modern language features like Promise.allSettled is a virtue. Happy coding!