Promise API

You already know how a single promise works with .then, .catch, and .finally. Those are instance methods: you call them on a promise you already have. The Promise class also carries six static methods, called on Promise itself. Most of them exist to combine several promises into one, which is where async code gets interesting: you rarely wait on just one thing.

Here’s the map before we walk through each one.

Wait for many → one result
Promise.allPromise.allSettledPromise.racePromise.any
Wrap a value → one promise
Promise.resolvePromise.reject
The six static Promise methods, grouped by what they do.

Promise.all

Suppose you need several async jobs to run at the same time and you want to continue only once every one of them is done. Fetch three URLs in parallel, then process all the responses together. That’s the job for Promise.all.

let promise = Promise.all(iterable);

You pass it an iterable — almost always an array of promises — and you get back a single new promise. That new promise fulfills when every promise in the array has fulfilled, and its result is an array of all their results.

Promise.all([
  new Promise(resolve => setTimeout(() => resolve("red"), 3000)),   // "red"
  new Promise(resolve => setTimeout(() => resolve("green"), 2000)), // "green"
  new Promise(resolve => setTimeout(() => resolve("blue"), 1000))   // "blue"
]).then(alert); // red,green,blue — each promise contributes one array member

The whole thing settles after 3 seconds (the slowest promise), and the result is ["red", "green", "blue"].

One detail that trips people up: the order of results matches the order of the input promises, not the order in which they finished. The first promise here is the slowest, yet its value "red" still sits at index 0. Promise.all keeps a slot for each input and fills it when that specific promise resolves.

time →1s 2s 3sp → bluep → greenp → redPromise.all → [red, green, blue]
Three promises run in parallel. Promise.all fulfills at the slowest one (3s), and results stay in source order.

Run this yourself. Each task gets a random delay, so they finish in a shuffled order every time — watch the “finished” column scramble. Yet the final array Promise.all produces always stays in source order A, B, C.

interactivePromise.all keeps results in source order

A common pattern: map data into promises

Most real uses of Promise.all start from an array of data — URLs, IDs, filenames — and map each item to a promise. Then you hand the whole array to Promise.all.

let urls = [
  'https://api.github.com/users/maya',
  'https://api.github.com/users/raj',
  'https://api.github.com/users/lena'
];

// map every url to the promise of the fetch
let requests = urls.map(url => fetch(url));

// Promise.all waits until all fetches are done
Promise.all(requests)
  .then(responses => responses.forEach(
    response => alert(`${response.url}: ${response.status}`)
  ));

A fuller version: fetch each GitHub user by name, then read every response body as JSON. The same shape works for fetching products by ID, or anything keyed by a list.

let names = ['maya', 'raj', 'lena'];

let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));

Promise.all(requests)
  .then(responses => {
    // every response arrived successfully
    for(let response of responses) {
      alert(`${response.url}: ${response.status}`); // 200 for each url
    }

    return responses;
  })
  // turn each response into the promise of its parsed JSON, then wait for all of them
  .then(responses => Promise.all(responses.map(r => r.json())))
  // now "users" is the array of parsed bodies
  .then(users => users.forEach(user => alert(user.name)));

Notice Promise.all appears twice. The first waits for the network responses to arrive; the second waits for each body to be parsed, because response.json() is itself async. Nesting Promise.all calls like this is idiomatic.

One rejection sinks the whole thing

If any promise in the array rejects, the promise returned by Promise.all rejects immediately with that same error. It does not wait for the rest.

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(10), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Boom!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(30), 3000))
]).catch(alert); // Error: Boom!

The second promise rejects at 2 seconds. At that instant Promise.all rejects too, so .catch fires with Error: Boom!. The third promise (which would have resolved at 3 seconds) never gets to contribute anything.

time →resolve 10reject !!✕ Boom!resolve 30(ignored)Promise.all rejects here (2s)
With Promise.all, the first rejection short-circuits the combined promise. The still-running promises keep going but are ignored.

Promise.allSettled

Promise.all is all-or-nothing: one failure rejects everything. That’s exactly what you want when later steps need every result to exist:

Promise.all([
  fetch('/template.html'),
  fetch('/style.css'),
  fetch('/data.json')
]).then(render); // render needs all three

But sometimes a partial result is still useful. Say you’re loading several users’ profiles for a dashboard. If one request fails, you’d rather show the ones that worked than blank the whole page. That’s where Promise.allSettled comes in.

Promise.allSettled waits for every promise to settle — fulfilled or rejected, it doesn’t matter — and never rejects. Its result is an array where each entry describes one outcome:

  • {status: "fulfilled", value: result} for a promise that fulfilled,
  • {status: "rejected", reason: error} for one that rejected.
fetch A ✓{ status: “fulfilled”, value: Response }
fetch B ✓{ status: “fulfilled”, value: Response }
fetch C ✕{ status: “rejected”, reason: Error }
allSettled always fulfills. Each input becomes a status object — value on success, reason on failure.

Here we fetch three URLs, one of which is deliberately broken:

let urls = [
  'https://api.github.com/users/maya',
  'https://api.github.com/users/raj',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { // (*)
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[num]}: ${result.reason}`);
      }
    });
  });

The results array at line (*) looks like this:

[
  {status: 'fulfilled', value: ...response...},
  {status: 'fulfilled', value: ...response...},
  {status: 'rejected', reason: ...error object...}
]

Every promise reports back — you get a status plus either a value or a reason for each one. No single failure hides the successes.

Flip any of these three “requests” to fail, then run. Notice the combined promise still fulfills — you always get one status object per input, and the successes survive next to the failures.

interactiveallSettled never rejects — every outcome comes back

Polyfill

Promise.allSettled is widely supported now, but if you target an environment that lacks it, the polyfill is short:

if (!Promise.allSettled) {
  const rejectHandler = reason => ({ status: 'rejected', reason });

  const resolveHandler = value => ({ status: 'fulfilled', value });

  Promise.allSettled = function (promises) {
    const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
    return Promise.all(convertedPromises);
  };
}

The trick is to make failures stop being failures. promises.map runs Promise.resolve(p) on each item (in case a raw value was passed instead of a promise), then attaches a .then with both handlers. A fulfilled value becomes {status:'fulfilled', value}; a rejection reason becomes {status:'rejected', reason}. Because the reject handler returns a normal object, the promise it produces fulfills — so none of the converted promises ever reject. That means the wrapping Promise.all never short-circuits, and you get every result back.

Promise.race

Promise.race is like Promise.all, except it settles as soon as the first promise settles — and it takes on that promise’s result or error, ignoring everyone else.

let promise = Promise.race(iterable);
Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(7), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Boom!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(9), 3000))
]).then(alert); // 7

The first promise resolves fastest (1 second), so its value 7 wins the race and becomes the result. The rejection at 2 seconds and the resolve at 3 seconds are both discarded.

The name is literal: whichever promise crosses the finish line first decides the outcome. Note the “first to settle” part — if the fastest one had rejected, Promise.race would reject. It doesn’t prefer success.

time →resolve 7winnerreject !!(ignored)resolve 9(ignored)race → 7 (at 1s)
race adopts the first promise to settle — success or failure — and drops the rest.

Promise.any

Promise.any is a close cousin of race, with one difference: it waits for the first promise to fulfill, skipping over rejections. If a fast promise rejects, any shrugs and keeps waiting for a successful one.

let promise = Promise.any(iterable);
Promise.any([
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Boom!")), 1000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(7), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(9), 3000))
]).then(alert); // 7

The first promise is fastest but it rejects — so any ignores it. The next to fulfill is the second promise at 2 seconds, and its value 7 becomes the result.

The single most instructive case is when the fastest promise rejects. Toggle that below and run the same three promises through both methods side by side: race adopts that fast rejection, while any skips it and waits for the first success.

interactiverace vs any when the fastest promise rejects

What if every promise rejects? Then Promise.any rejects too — but with a special error, an AggregateError, that bundles all the individual failures in its errors property (an array).

Promise.any([
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ping failed")), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Pong failed")), 2000))
]).catch(error => {
  console.log(error.constructor.name); // AggregateError
  console.log(error.errors[0]); // Error: Ping failed
  console.log(error.errors[1]); // Error: Pong failed
});

Every rejection is preserved. error.errors is an array holding the reason each promise gave, in source order — so you can inspect what went wrong across the board, not just one failure.

Promise.resolve / reject

The last two are the odd ones out — they don’t combine anything. Each wraps a single value in a promise. You’ll reach for them far less often now, because async/await (covered a bit later) usually makes them unnecessary. They’re here for completeness and for code that can’t use async/await.

Promise.resolve

Promise.resolve(value) returns a promise that’s already fulfilled with value. It’s shorthand for:

let promise = new Promise(resolve => resolve(value));

Where does this help? When a function is expected to return a promise, and sometimes it has the answer synchronously. You still want the caller to be able to write .then(), so you wrap the ready value.

Take a loadCached function that fetches a URL and caches the text. On the first call it fetches; on later calls with the same URL it returns the cached text — but wrapped in a promise, so the return type stays consistent:

let cache = new Map();

function loadCached(url) {
  if (cache.has(url)) {
    return Promise.resolve(cache.get(url)); // (*)
  }

  return fetch(url)
    .then(response => response.text())
    .then(text => {
      cache.set(url, text);
      return text;
    });
}

Because line (*) wraps the cached string in a promise, loadCached(url) always returns a promise. The caller never has to check whether the result came from the cache or the network — loadCached(url).then(…) works either way.

Promise.reject

Promise.reject(error) returns a promise that’s already rejected with error. Shorthand for:

let promise = new Promise((resolve, reject) => reject(error));

In practice you’ll almost never write this one directly.

Summary

The Promise class has six static methods:

  1. Promise.all(promises) — waits for all to fulfill, resolves to an array of results in source order. If any promise rejects, that error becomes the result of Promise.all and the other results are discarded.
  2. Promise.allSettled(promises) — waits for all to settle, never rejects. Returns an array of objects, each with status ("fulfilled" or "rejected") and either value or reason.
  3. Promise.race(promises) — settles on the first promise to settle; its result or error becomes the outcome.
  4. Promise.any(promises) — settles on the first promise to fulfill; its value becomes the outcome. If all reject, it rejects with an AggregateError whose errors array holds every reason.
  5. Promise.resolve(value) — a promise already fulfilled with value.
  6. Promise.reject(error) — a promise already rejected with error.
MethodWaits forRejects when
allevery promise to fulfillany one rejects
allSettledevery promise to settlenever
racethe first to settlethe first settle is a reject
anythe first to fulfillevery promise rejects
Quick decision guide for the four combinators.

Of the six, Promise.all is the one you’ll write most often.