All Articles

JavaScript: Promise combinators

Promise combinators

Promises aren’t a new concept in JavaScript. They are objects that represent the eventual completion or failure of an asynchronous operation, and its resulting value.

There are three possible states for promises: pending – initial state (still waiting), fulfilled – promise succeeded and rejected – promise failed. If the promise is either fulfilled or rejected, but not pending, the promise is said to be settled.

Promise combinators are used for easier dealing with promises. JavaScript has four promise combinators but they are in different stages of ECMAScript.

Promise.all()

Promise.all() is a method that takes an iterable object of promises and returns a single Promise. The returned Promise is fulfilled if all of the promises are fulfilled.

const P1 = Promise.resolve(18);
const P2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(5);
  }, 100);
});
const P3 = Promise.resolve("18.5.");
const promises = [P1, P2, P3];

Promise.all(promises).then((values) => {
  console.log(values);
});
//Array [18, 5, "18.5."]

Or, with async/await:

async function f() {
  const P1 = Promise.resolve(18);
  const P2 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(5);
    }, 100);
  });
  const P3 = Promise.resolve("18.5.");
  const promises = [P1, P2, P3];
  try {
    const result = await Promise.all(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //Array [18, 5, "18.5."]

The order in the returned Promise will always stay the same as in its source promises even if the second Promise took the longest time to resolve.

If an empty object is passed then it returns an already resolved Promise.

Promise.all() accepts non-promise values too and returns them as is.

async function f() {
  const promises = [1, 2, 3];
  try {
    const result = await Promise.all(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //Array [1, 2, 3]

The returned Promise is rejected if at least one Promise is rejected. The error becomes the result of the entire Promise.all() method. The results of the other promises are completely ignored.

async function f() {
  const P1 = Promise.resolve(18);
  const P2 = Promise.resolve(5);
  const P3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("Error happened!"));
    }, 200);
  });
  const promises = [P1, P2, P3];
  try {
    const result = await Promise.all(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //Error: Error happened!

Promise.all() is part of JavaScript since of introduction of promises in ES2015 together with one more static method for promises: Promise.race()

Promise.race()

Promise.race() returns first settled Promise from the received iterable object of promises. That can be fulfilled Promise.

async function f() {
  const P1 = Promise.resolve(18);
  const P2 = Promise.resolve(5);
  const P3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("18.5.");
    }, 100);
  });
  const promises = [P1, P2, P3];
  try {
    const result = await Promise.race(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //18

Or it can be a rejected Promise.

async function f() {
  const P1 = Promise.reject(new Error("Error happened!"));
  const P2 = Promise.resolve(5);
  const P3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("18.5.");
    }, 100);
  });
  const promises = [P1, P2, P3];
  try {
    const result = await Promise.race(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //Error: Error happened!

If an empty object is passed the returned Promise will be pending forever.

If Promise.race() receives non-promise values it will resolve to the first of the values found in the iterable.

async function f() {
  const promises = [1, 2, 3];
  try {
    const result = await Promise.race(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //1

Promise.allSettled()

Promise.allSettled() method is added in the last ES2020. Old browsers may still need polyfills to support it. This method waits for all the received promises to settle. It returns a Promise with:

  • { status: “fulfilled”, value: result } for successful responses or
  • { status: “rejected”, reason: error} for responses with an error.
async function f() {
  const P1 = Promise.reject(new Error("Error happened!"));
  const P2 = 5;
  const P3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("18.5.");
    }, 100);
  });
  const promises = [P1, P2, P3];
  try {
    const result = await Promise.allSettled(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //Array [
//Object { status: "rejected", reason: Error: Error happened! },
//Object { status: "fulfilled", value: 5 },
//Object { status: "fulfilled", value: "18.5." }]

As we can see in the example above method can receive non-promise values too.

If an empty iterable object is passed Promise.allSettled() returns an already resolved Promise object as an empty Array.

async function f() {
  try {
    const result = await Promise.allSettled([]);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //Array []

Promise.any()

Promise.any() is the latest promise combinator which is now in Stage 3 of the TC39 process. It takes an iterable object and as soon one of the Promises is fulfilled returns that Promise.

async function f() {
  const P1 = Promise.resolve(18);
  const P2 = Promise.resolve(5);
  const P3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("18.5.");
    }, 100);
  });
  const promises = [P1, P2, P3];
  try {
    const result = await Promise.any(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //18

The difference between this method and Promise.all() is that Promise.all() returns an array of fulfillment values while Promise.any() returns just the first fulfillment value. Promise.race() is different than Promise.any() because Promise.race() returns the first settled value (either fulfilled or rejected) while Promise.any() ignores rejected promises up until the first promise that is fulfilled.

async function f() {
  const P1 = Promise.reject(new Error("Error happened!"));
  const P2 = Promise.resolve(5);
  const P3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("18.5.");
    }, 100);
  });
  const promises = [P1, P2, P3];
  try {
    const result = await Promise.any(promises);
    console.log(result);
  } catch (err) {
    console.log(err);
  }
}
f(); //5

If an empty iterable is passed the method returns an already resolved promise.

If all of the given promises are rejected the method asynchronously rejects with an AggregateError, a new Error subclass that groups together individual errors.

Promise.any() is still in the experimental phase and it is not yet fully supported by browsers.

Like what you've read? Join our newsletter

Launching a new JavaScript project? Need help on an existing project? Work with us