Skip to Content
Course content

61: Promise.all, allSettled, race, and any

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Alright, let's get into this. Imagine we're building a user dashboard. To get the page ready, we need to fetch three things: the user's profile, their recent orders, and their notification settings. We could just await each one sequentially, but that's a waste of time. Why wait for the profile to finish before even starting the order request?

Trying to speed things up

The obvious move is to fire them all off at once. I'll throw them into an array and use Promise.all. This is usually the go-to for "I need all of this data before I can render the UI."

const fetchDashboardData = async () => {
  const profilePromise = fetch('/api/profile');
  const ordersPromise = fetch('/api/orders');
  const settingsPromise = fetch('/api/settings');

  try {
    const [profile, orders, settings] = await Promise.all([
      profilePromise, 
      ordersPromise, 
      settingsPromise
    ]);
    console.log('Everything loaded!', { profile, orders, settings });
  } catch (error) {
    console.error('Something went wrong:', error);
  }
};

This looks great on paper. It's fast because the requests happen in parallel. But here's the catch: Promise.all is "fail-fast." I just tried this in my local environment and intentionally broke the /api/settings endpoint. Even though the profile and orders came back perfectly, the whole thing jumped straight to the catch block. The user sees a total error page just because their notification settings failed to load. That's a bad user experience.

Handling partial failures

I don't actually need the settings to render the profile or the orders. I just want whatever actually worked. This is where Promise.allSettled comes in. Let's swap it out and see how the output changes.

const fetchDashboardData = async () => {
  const promises = [
    fetch('/api/profile'),
    fetch('/api/orders'),
    fetch('/api/settings')
  ];

  const results = await Promise.allSettled(promises);
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Request ${index} succeeded:`, result.value);
    } else {
      console.log(`Request ${index} failed:`, result.reason);
    }
  });
};

Notice the difference? No try/catch block is strictly necessary here because allSettled never rejects; it just tells you the outcome of every single promise. It gives us an array of objects with a status of either 'fulfilled' or 'rejected'. Now, I can render the profile and orders, and maybe just show a little warning icon where the settings should be. Much better.

The race for the fastest response

Now, let's change the scenario. Say we have our data mirrored on two different servers—a primary and a backup—to ensure low latency. We don't need both; we just want whichever one responds first. This feels like a race.

const fastFetch = async () => {
  const primaryServer = fetch('https://primary.api.com/data');
  const backupServer = fetch('https://backup.api.com/data');

  try {
    const fastest = await Promise.race([primaryServer, backupServer]);
    console.log('Winner:', fastest);
  } catch (error) {
    console.error('The fastest one failed:', error);
  }
};

Promise.race is simple: the first promise to settle (whether it succeeds or fails) wins. But wait—I just noticed a flaw. If the primary server is incredibly fast but returns a 404 Error, Promise.race will resolve with that failure immediately. It won't wait for the backup server to potentially provide a successful response. It's literally a race to the finish line, regardless of whether the finish line is a trophy or a cliff.

Finding the first success

If I only care about the first successful response and want to ignore the failures unless everything fails, Promise.any is the tool I'm looking for. It's like the opposite of Promise.all.

const reliableFetch = async () => {
  const primaryServer = fetch('https://primary.api.com/data');
  const backupServer = fetch('https://backup.api.com/data');

  try {
    const firstSuccess = await Promise.any([primaryServer, backupServer]);
    console.log('First successful response:', firstSuccess);
  } catch (error) {
    console.log('All servers failed');
    // Promise.any throws an AggregateError when all promises reject
  }
};

Now, if the primary server crashes, JavaScript just ignores it and waits for the backup. It only throws an error if every single promise in the array rejects. It's a much more resilient pattern for redundancy.




📋 Practical Task

Build a Multi-Source Image Resolver

You are building a gallery that loads images from three different CDN mirrors to ensure the user sees the image as quickly as possible. Some mirrors might be down, and some might be slow.

Your Task:

  • Create a function resolveImage(urls) that takes an array of image URLs.
  • Use the appropriate Promise method to return the first successfully resolved URL from the list.
  • If all mirrors fail, the function should catch the error and return a string: "fallback-image.jpg".
  • To simulate the network, create a helper function mockFetch(url) that randomly returns a resolved promise (success) or a rejected promise (failure) after a random delay between 100ms and 1000ms.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.