Multiple promises with async-await

What is convenient about using Promise.all is that it uses an array to gather all of the promises together, so that since we await the resolution of all of these promises, when the result comes back, we will get the result all together at the same time. This can be an improvement over get one result from one promise and putting in an variable, getting the result from the next and putting it in a variable and so on.

async function concurrent() {
  const p1 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 500, "1st");
  });
  const p2 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 1000, "2nd");
  });
  const p3 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 200, "3rd");
  });
  const result = await Promise.all([p1, p2, p3]);
  console.log(result);
}

concurrent();

Error handling

What if one of these promises fails?

If any of the passed-in promises reject, Promise.all asynchronously rejects with the value of the promise that rejected, whether or not the other promises have resolved.

In other words, Promise.all waits for all to be fulfilled (or the first rejection).

async function concurrent() {
  const p1 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 500, "1st");
  });
  const p2 = new Promise((resolve, reject) => {
    setTimeout((text) => reject(new Error("Oops")), 1000, "2nd");
  });
  const p3 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 200, "3rd");
  });
  const result = await Promise.all([p1, p2, p3]);
  console.log(result);
}

concurrent().catch((err) => console.error(err)); // Error: Oops

Promise.allSettled

This behavior of Promise.all could be a problem however. What if sometimes we will get an error from one of the requests, but we need the two other values. Promise.all isn’t great because in our example, the two other promises that were resolved successfully were just thrown away. How do we keep them.

Fortunately there is a very new addition to the language that was made for such an event—that even if one of the requests fails, the promises resolve as if it was successful, so you do get those values that came back successfully. All we have to do is replace Promise.all with Promise.allSettled to see this in action:

async function concurrent() {
  const p1 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 500, "1st");
  });
  const p2 = new Promise((resolve, reject) => {
    setTimeout((text) => reject(new Error("Oops")), 1000, "2nd");
  });
  const p3 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 200, "3rd");
  });
  const result = await Promise.allSettled([p1, p2, p3]);
  console.log(result);
}

concurrent().catch((err) => console.error(err));

Clarification

Promises are executed at the moment of creation. (can be confirmed by running a bit of code). In new Promise(a).then(b); c(); a is executed first, then c, then b. It isn't Promise.all that runs these promises, it just handles when they resolve.

Promise.all only await multiple promises. It doesn't care in what order they resolve, or whether the computations are running in parallel.

So be aware that really just gives you another way of resolving the problems. Promise.all doesn’t do anything special to your promises, it just allows you to await them all at the same time and get their resolved values all at once.

To prove that Promise.all doesn’t do anything special with our promises as compared to awaiting all of them individually:

async function fn() {
  const p1 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 1000, "1st");
  });
  const p2 = new Promise((resolve, reject) => {
    setTimeout((text) => resolve(text), 2000, "2nd");
  });
  const p3 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 3000, "3rd");
  });
  const res1 = await p1;
  console.log(res1);
  const res2 = await p2;
  console.log(res2);
  const res3 = await p3;
  console.log(res3);
}

fn(); // 1st (after 1 second), 2nd (after 2 seconds), 3rd (after 3 seconds)

Promise.race

And one final feature that Promise makes available to use is the ability to resolve just the first promise out of a set of them that comes back successfully. Whichever one resolves first is the one returned. This is known as a race condition and the method that we use for it is Promise.race. It works exactly like Promise.all and Promise.settled:

async function race() {
  const p1 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 500, "1st");
  });
  const p2 = new Promise((resolve, reject) => {
    setTimeout((text) => reject(new Error("Oops")), 1000, "2nd");
  });
  const p3 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 200, "3rd");
  });
  const result = await Promise.race([p1, p2, p3]);
  console.log(result);
}

race().catch((err) => console.error(err));

Using this example, we can take a look at this code and figure out which one will come back first. Can you figure it out? Let’s see:

It’s the 3rd one, since the other two promises had a longed time to wait to be resolved in their setTimeout. With our current code, the 3rd promise should always come back as the result.

Promise.race behaves just like Promise.all in terms of its error handling, so if the first resolved promise to come back is rejected, then it will resolve to an error and any other promises will be ignored

We can see this if we change the timeout of the second promise to just 100 ms from 1000 ms:

async function race() {
  const p1 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 500, "1st");
  });
  const p2 = new Promise((resolve, reject) => {
    setTimeout((text) => reject(new Error("Oops")), 100, "2nd");
  });
  const p3 = new Promise((resolve) => {
    setTimeout((text) => resolve(text), 200, "3rd");
  });
  const result = await Promise.race([p1, p2, p3]);
  console.log(result);
}

race().catch((err) => console.error(err)); // Error: Oops