Promise.all vs Promise.allSettled: Which One Should You Use?

May 23, 2026

Here's a pattern you've probably written or seen before:

let refreshAll = useCallback(async function() {
  let fns = [...refetchersRef.current];
  await Promise.all(fns.map(fn => fn().catch(() => undefined)));
}, []);

The intent is clear: fire all the refetch functions, don't let one failure cancel the others, and wait for everything to finish. But the implementation is doing something a little roundabout. This came up in a code review recently, and it's worth unpacking.

What's actually happening here

Each promise has its own .catch(() => undefined), which swallows rejections and converts them into resolved promises. So by the time Promise.all sees them, every promise has already resolved and there's nothing left to reject.

In other words, the code is manually reimplementing the behavior of a built-in that already exists.

Enter Promise.allSettled

Promise.allSettled was introduced in ES2020 precisely for this use case: wait for all promises to finish, regardless of whether they resolve or reject.

let refreshAll = useCallback(async function() {
  let fns = [...refetchersRef.current];
  await Promise.allSettled(fns.map(fn => fn()));
}, []);

That's it. No per-promise .catch(), no converting rejections into undefined. The name itself communicates the intent: "settle" means each promise runs to completion, pass or fail.

The bonus: you can actually inspect failures

Unlike the .catch(() => undefined) pattern, allSettled doesn't discard failure information. Each result comes back as a tagged object:

{ status: 'fulfilled', value: ... }
{ status: 'rejected', reason: ... }

So if you ever want to know which refetchers failed and why, the door is still open:

let refreshAll = useCallback(async function() {
  let fns = [...refetchersRef.current];
  let results = await Promise.allSettled(fns.map(fn => fn()));

  results.forEach(function(r) {
    if (r.status === 'rejected') console.warn('refetcher failed:', r.reason);
  });
}, []);

With the original pattern, that information is gone the moment .catch(() => undefined) runs.

Why someone might still write Promise.all + .catch

  • Compatibility: allSettled requires ES2020 (Node 12.9+, modern browsers). In very old environments or strict compile targets, Promise.all was the safer choice. Not really a concern in 2026, but older codebases may reflect this history.
  • Habit: Promise.all is what many developers learned first. The .catch pattern gets the job done, so it sticks.
  • Explicitness: Some developers like that .catch(() => undefined) makes the "I don't care about this error" decision visible at the call site. Fair, though allSettled makes the same statement more clearly at the API level.

The bottom line

Promise.all is for when you want all-or-nothing semantics: succeed together or fail fast. Promise.allSettled is for when you want to wait on everything regardless of outcome.

For a refreshAll utility, where the whole point is that no single failure should block the others, allSettled is the right tool. It's shorter, more readable, and preserves information you might want later. The Promise.all + .catch version works, but it's working around a gap that no longer exists.