Promise.all() vs Promise.allSettled() in JS: The little-known difference

Last updated on August 20, 2024
Promise.all() vs Promise.allSettled() in JS: The little-known difference

I can't believe some developers think they are interchangeable.

They are not.

all() and allSettled() are much more different than you think.

So let's say I decided to singlehandedly create my own ambitious new music app to finally take down Spotify.

I've worked tirelessly to make several music deals and have all the backend data ready.

Now I'm creating the app's homepage: a shameless carbon copy of Spotify showing off daily mixes, recently played, now playing, and much more.

All this data will come from different API URLs.

Of course I wouldn't be naive enough to fetch from all the URLs serially and create a terrible user experience (or would I?)

async function loadHomePage() {
  const apiUrl = 'music.tariibaba.com/api';
  const dailyMixesUrl = `${apiUrl}/daily`;
  const nowPlayingUrl = `${apiUrl}/nowPlaying`;

  // ❌ Serial loading
  const dailyMixes = await (
    await fetch(dailyMixesUrl)
  ).json();

  const nowPlaying = await (
    await fetch(nowPlayingUrl)
  ).json();

  return { dailyMixes, nowPlaying };
}

No I wouldn't.

So instead I use use Promise.all() to fetch from all these URLs at once:

Much better, right?

async function loadHomePage() {
  const apiUrl = 'music.tariibaba.com/api';
  const dailyMixesUrl = `${apiUrl}/daily`;
  const nowPlayingUrl = `${apiUrl}/nowPlaying`;

  // Promise.all() ??
  await Promise.all([
    fetch(dailyMixesUrl),
    fetch(nowPlayingUrl),
  ]);

  return { dailyMixes, nowPlaying };
}

Wrong.

I just made the classic developer error of forgetting to handle the error state.

Let me tell you something: When Promise.all() fails, it fails:

async function loadHomePage() {
  const apiUrl = 'music.tariibaba.com/api';
  const dailyMixesUrl = `${apiUrl}/daily`;
  const nowPlayingUrl = `${apiUrl}/nowPlaying`;

  // Promise.all() ??
  try {
    await Promise.all([
      fetch(dailyMixesUrl),
      fetch(nowPlayingUrl),
    ]);
  } catch (err) {
    // ❌ Which failed & which succeeded? We have no idea
    console.log(`error: ${err}`);
  }

  return { dailyMixes, nowPlaying };
}

What do you when one of those requests fail? Am I just going to leave that UI section blank and not try again?

But look what Promise.allSettled() would have done for us:

async function loadHomePage() {
  const apiUrl = 'music.tariibaba.com/api';
  const dailyMixesUrl = `${apiUrl}/daily`;
  const nowPlayingUrl = `${apiUrl}/nowPlaying`;
  // ...

  // Promise.all() ??
  try {
    const promises = await Promise.allSettled([
      fetch(dailyMixesUrl),
      fetch(nowPlayingUrl),
      // ...
    ]);
    const succeeded = promises.filter(
      (promise) => promise.status === 'fulfilled'
    );
    const failed = promises.filter(
      (promise) => promise.status === 'rejected'
    );
    // ✅ now retry failed API requests until succeeded
  } catch (err) {
    // We don't need this anymore!
    console.log(`error: ${err}`);
  }

  return { dailyMixes, nowPlaying };
}

Now I could easily set up a clever system to tirelessly retry all the failed requests until they all work.

async function loadHomePage() {
  const apiUrl = 'music.tariibaba.com/api';
  const dailyMixesUrl = `${apiUrl}/daily`;
  const nowPlayingUrl = `${apiUrl}/nowPlaying`;

  // Promise.all() ??
  const pending = [
    () => fetch(dailyMixesUrl),
    () => fetch(nowPlayingUrl),
  ];

  while (pending.length > 0) {
    const promises = await Promise.allSettled(
      pending.map((pending) => pending())
    );
    const newPending = [];
    promises.forEach((promise, index) => {
      if (promise.status === 'rejected') {
        newPending.push(pending[index]);
      }
    });
    pending = newPending;
  }

  return { dailyMixes, nowPlaying };
}

When all() is better

This is great, but I need to update a lot of data when a user plays a song.

The song's stream count, the user's history, recently played...

But the database I'm using is horrible and doesn't have support for transactions / batched writes.

I need to find a way to make sure I can update all the data in their separate locations at the exact same time.

Luckily, Promise.all() is useful this time.

It's all or nothing.

async function runTransaction(updates) {
  // updates are a list of DB actions
  try {
    // store db state, somehow...
    await Promise.all(updates);
  } catch (err) {
    // intelligently revert to previous state, somehow...
  }
}

With all() I'm confident that if a single updates fails, it's over.

And now I can even bring my smart auto-retry system here, but this time everything is getting retried, after this reversal.

Final thoughts

Promise.all() -- If one of us fails, we all fail.

Promise.allSettled() -- You are all on your own.

Coding Beauty Assistant logo

Try Coding Beauty AI Assistant for VS Code

Meet the new intelligent assistant: tailored to optimize your work efficiency with lightning-fast code completions, intuitive AI chat + web search, reliable human expert help, and more.

See also