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!