Back to Blog
JavaScriptJune 26, 20264 min read

JavaScript Promise.allSettled: Mastering Async Error Handling

Master JavaScript Promise.allSettled to prevent app crashes during partial failures. Learn robust patterns for handling promise errors and async concurrency.

JavaScriptPromisesAsyncDebuggingWeb DevelopmentFrontend

During a recent refactor, I spent three hours tracking down a "silent" UI failure that turned out to be a classic Promise.all trap. We were firing off five parallel API requests to populate a dashboard, and when one failed, the entire batch rejected, leaving the user with an empty screen and a console full of unhandled promise rejections.

If you're tired of your application state breaking because one minor request failed, it's time to stop using Promise.all for everything and start using JavaScript Promise.allSettled.

Why Promise.all Fails You

The issue with Promise.all is its "fail-fast" behavior. It waits for all promises to resolve, but the moment a single promise rejects, the entire returned promise rejects immediately. In a production environment, this is rarely what you want when fetching independent data points.

We first tried adding a .catch() to each individual promise within the Promise.all array to "swallow" the errors. It worked, but it was messy. Our code looked like a nest of error-handling logic that felt fragile. That’s when we switched to Promise.allSettled.

Implementing JavaScript Promise.allSettled

Unlike its predecessor, Promise.allSettled waits for every promise to complete, regardless of whether they fulfilled or rejected. It returns an array of objects describing the outcome of each promise.

Here is how you implement it in a real-world scenario:

JAVASCRIPT
const requests = [
  fetch(CE9178">'/api/user'),
  fetch(CE9178">'/api/settings'),
  fetch(CE9178">'/api/notifications')
];

const results = await Promise.allSettled(requests);

results.forEach((result, index) => {
  if (result.status === CE9178">'fulfilled') {
    console.log(CE9178">`Request ${index} succeeded:`, result.value);
  } else {
    console.error(CE9178">`Request ${index} failed:`, result.reason);
  }
});

This pattern is significantly cleaner. You get an array where each object has a status property (either 'fulfilled' or 'rejected'). If it's fulfilled, you get the value; if it's rejected, you get the reason. This is the gold standard for async JavaScript debugging because you no longer lose context on which request failed.

Handling Promise Errors Gracefully

When you're dealing with partial failures, you need a strategy for the UI. If the user's notifications fail to load, do you really want to hide their profile information too? Probably not.

When building features like this, I often find that Managing Errors: Professional Error Handling in React is the next logical step. Once you have the results from allSettled, you should map them to your application state.

FeaturePromise.allPromise.allSettled
BehaviorFail-fastWait-for-all
Use CaseDependent tasksIndependent tasks
ResultSingle value or errorArray of outcome objects
Error HandlingRequires individual catchHandled via status check

Avoiding Unhandled Promise Rejections

One common trap developers fall into is forgetting that even with allSettled, the individual promises might still trigger global error listeners if they aren't caught. If you pass a promise that doesn't have its own .catch() attached, some environments might still flag it as an unhandled promise rejection.

Always wrap your asynchronous calls in a utility that ensures each promise is "safe":

JAVASCRIPT
const safeFetch = (url) => fetch(url).catch(err => ({ error: err }));

By normalizing your output before passing it to Promise.allSettled, you ensure your JavaScript concurrency patterns remain predictable and your logs stay clean. If you're working on complex forms, remember that Optimizing Form Submissions: UX, Errors, and API Handling relies on similar patterns to ensure that one failing request doesn't ruin the entire user journey.

FAQ

What happens if I use Promise.allSettled with an empty array?

It returns an empty array immediately. It doesn't throw an error, which makes it safe to use in dynamic loops where your data array might sometimes be empty.

Can I mix non-promise values?

Yes. Promise.allSettled treats non-promise values as already-fulfilled promises. They will appear in the resulting array with a status of 'fulfilled' and the value as the result.

Is this supported in all browsers?

It’s supported in all modern browsers (Chrome 76+, Firefox 71+, Safari 13+). If you're targeting legacy browsers like Internet Explorer, you will definitely need a polyfill, though honestly, it might be time to move on from those environments.

Final Thoughts

I'm still occasionally tempted to reach for Promise.all when I'm feeling lazy and want the "fail-fast" behavior, but I usually regret it within an hour of testing. Promise.allSettled is just more robust for real-world production code. It forces you to acknowledge that networks are unreliable and that your UI should be resilient enough to handle partial success. Next time, I might look into adding more granular retry logic for the rejected promises, but for now, this pattern has saved me from plenty of late-night debugging.

Similar Posts