Skip to main content

🧰 Promise Static Methods

Beyond creating and chaining individual Promises, the Promise class itself offers a toolbox of static methods for coordinating whole groups of asynchronous work. Learn resolve, reject, all, race, allSettled, and any — and exactly when to reach for each.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Use Promise.resolve and Promise.reject to create pre-settled Promises
  • Coordinate parallel work with Promise.all and understand its fail-fast behavior
  • Apply Promise.race for timeouts and first-to-settle scenarios
  • Choose between Promise.allSettled (partial success) and Promise.any (first success)
  • Combine these methods into real-world patterns: concurrency limits, retries with backoff, and resilient loading

Estimated Time: 35–45 minutes  •  Difficulty: Intermediate

Hands-on: Build a concurrency-limited batch runner using the combinators.

In This Lesson

What Are Static Methods?

Static methods are called on the Promise constructor itself — Promise.all(...) — rather than on an individual Promise instance like myPromise.then(...). Their job is to coordinate: take a collection of Promises and combine them into a single Promise that settles according to a specific rule.

Four of them (all, race, allSettled, any) are called combinators because they combine many Promises into one. The other two (resolve, reject) are convenience factories for Promises that are already settled.

graph TD A[Promise class] --> B[Promise.resolve] A --> C[Promise.reject] A --> D[Promise.all] A --> E[Promise.race] A --> F[Promise.allSettled] A --> G[Promise.any]

Pre-Settled Promises: resolve & reject

Promise.resolve(value)

Creates a Promise that is already fulfilled with value. It's the go-to when you need to guarantee a function always returns a Promise, even on a synchronous fast path like a cache hit.

💡 Analogy: Promise.resolve() is a pre-signed contract. There's no negotiation to wait through — the deal is already done and ready to use.
function getUser(userId) {
  const cached = cache.get(userId);
  if (cached) {
    return Promise.resolve(cached); // fast path, still returns a Promise
  }
  return fetch(`/api/users/${userId}`).then((r) => r.json());
}

// Callers can always rely on .then(), regardless of which path ran
getUser(7).then((user) => console.log(user.name));

Promise.reject(reason)

The mirror image: a Promise that is already rejected. Handy for early-exit validation, so the caller's .catch() handles the failure consistently.

function processInput(input) {
  if (!input) {
    return Promise.reject(new Error('Input cannot be empty'));
  }
  return asyncProcess(input);
}

processInput('')
  .then((result) => console.log(result))
  .catch((error) => console.error(error.message)); // "Input cannot be empty"

Promise.all — All or Nothing

Promise.all(iterable) waits for every Promise to fulfill, then resolves with an array of their values in the original order. If any single Promise rejects, all rejects immediately with that reason — a behavior called fail-fast.

💡 Analogy: Cooking a recipe that needs every ingredient before you start. If even one ingredient never arrives, the whole dish is off.
Promise.all fan-in Three parallel promises feed into Promise.all, which resolves with an array of all results only when every one succeeds, and rejects if any fails. Promise 1 Promise 2 Promise 3 Promise.all [r1, r2, r3] resolves if ALL succeed rejects if ANY fails
Figure 1 — Promise.all fans several Promises in and resolves with an ordered array — but only if none reject.
Promise.all([
  fetch('/api/user').then((r) => r.json()),
  fetch('/api/posts').then((r) => r.json()),
  fetch('/api/analytics').then((r) => r.json()),
])
  .then(([profile, posts, analytics]) => {
    renderDashboard(profile, posts, analytics);
  })
  .catch((error) => {
    showError('Failed to load dashboard data');
    console.error(error);
  });

⚠️ Two gotchas

Fail-fast loses partial results: if one call rejects, the successful results of the others are discarded. When partial success is acceptable, use Promise.allSettled instead.

An empty array resolves instantly with [] — not an error, but easy to overlook.

Promise.race — First to Settle Wins

Promise.race(iterable) settles as soon as the first input Promise settles — fulfilled or rejected — adopting that outcome. The other Promises keep running, but their results are ignored.

💡 Analogy: A footrace. The instant the first runner crosses the line, the race is over — whether they finished gloriously or tripped at the tape.

Its most common use is a timeout: race a real operation against a Promise that rejects after a delay.

function fetchWithTimeout(url, ms = 5000) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`Request timed out after ${ms}ms`)), ms);
  });
  return Promise.race([
    fetch(url).then((r) => r.json()),
    timeout,
  ]);
}

fetchWithTimeout('/api/data', 3000)
  .then((data) => console.log('Got data in time:', data))
  .catch((error) => console.error(error.message));
graph TD A[Start] --> B[Fetch data] A --> C[Timeout timer] B --> D{{Promise.race}} C --> D D --> E[First to settle wins]

Modern Combinators: allSettled & any

Promise.allSettled() (ES2020)

Waits for every Promise to settle and never rejects. It resolves with an array of result objects, each either { status: 'fulfilled', value } or { status: 'rejected', reason }. Use it when partial success is fine and you want a full report.

💡 Analogy: Sending several scouts down different paths. You wait for all of them to come back, then read every report — good news and bad.
const requests = endpoints.map((url) => fetch(url).then((r) => r.json()));

Promise.allSettled(requests).then((results) => {
  const succeeded = results
    .filter((r) => r.status === 'fulfilled')
    .map((r) => r.value);

  results
    .filter((r) => r.status === 'rejected')
    .forEach((r) => console.warn('One request failed:', r.reason));

  processAvailable(succeeded); // continue with whatever we got
});

Promise.any() (ES2021)

Resolves as soon as the first Promise fulfills, ignoring rejections. Only if every Promise rejects does it reject — with an AggregateError whose .errors array holds all the reasons.

💡 Analogy: A search party. You need just one person to find the target for the mission to succeed; it only fails if everyone comes back empty-handed.
// Try multiple mirrors; use whichever responds first
Promise.any([
  fetch('https://cdn1.example.com/data.json'),
  fetch('https://cdn2.example.com/data.json'),
  fetch('https://cdn3.example.com/data.json'),
])
  .then((response) => console.log('Fastest healthy mirror responded'))
  .catch((aggregateError) => {
    console.error('All mirrors failed:', aggregateError.errors);
  });
graph TD A[Promise.any of p1, p2, p3] --> B{{Any fulfills?}} B -->|Yes| C[Resolve with first success] B -->|No, all reject| D[Reject with AggregateError]

💡 race vs. any — the key difference

race takes the first Promise to settle (even a rejection). any takes the first to fulfill, ignoring failures until they're the only outcome left. Use race for timeouts; use any for "first success from redundant sources."

Choosing the Right One

All four combinators take an iterable of Promises; they differ only in when they settle and what they resolve to:

Method Resolves when Rejects when Result value Best for
Promise.all All fulfill Any rejects (fail-fast) Array of all values Needing every result
Promise.race First settles (fulfilled) First settles (rejected) Value/reason of first to settle Timeouts, fastest-wins
Promise.allSettled All settle Never Array of status objects Partial success is OK
Promise.any First fulfills All reject (AggregateError) First fulfilled value Redundant sources

The same four Promises fed to each method make the contrast concrete:

const promises = [
  Promise.resolve('Success 1'),
  Promise.reject('Error 1'),
  new Promise((res) => setTimeout(() => res('Success 2'), 1000)),
  new Promise((_, rej) => setTimeout(() => rej('Error 2'), 1500)),
];

Promise.all(promises).catch((e) => console.log('all →', e));
// all → Error 1          (fails fast on the first rejection)

Promise.race(promises).then((v) => console.log('race →', v));
// race → Success 1       (first to settle)

Promise.allSettled(promises).then((r) => console.log('allSettled →', r));
// allSettled → [ fulfilled, rejected, fulfilled, rejected ]

Promise.any(promises).then((v) => console.log('any →', v));
// any → Success 1        (first to FULFILL)

Real-World Patterns

Sequential vs. parallel

Independent work should run in parallel; dependent work must run in sequence. Choosing wrong is a top cause of sluggish apps.

// Parallel — independent fetches, much faster
async function loadAllInParallel(ids) {
  const files = await Promise.all(ids.map((id) => fetchFile(id)));
  return files.map(process);
}

// Sequential — each step needs the previous result
async function loadInSequence(ids) {
  const results = [];
  for (const id of ids) {
    const file = await fetchFile(id);   // waits each time
    results.push(await process(file));
  }
  return results;
}

Limiting concurrency

Firing 500 requests at once can overwhelm a server or hit rate limits. This runner keeps at most limit in flight, using Promise.race to wait for a slot to free up:

async function withConcurrency(items, limit, task) {
  const results = [];
  const running = new Set();

  for (const item of items) {
    const p = Promise.resolve(task(item)).then((result) => {
      running.delete(p);
      return result;
    });
    running.add(p);
    results.push(p);

    if (running.size >= limit) {
      await Promise.race(running); // wait for any one to finish
    }
  }
  return Promise.all(results);
}

// Process 100 images, at most 5 at a time
const ids = Array.from({ length: 100 }, (_, i) => `img_${i}`);
withConcurrency(ids, 5, (id) => processImage(id))
  .then((all) => console.log(`Done: ${all.length}`));

Retry with exponential backoff

Transient failures (a flaky network) often succeed on a second try. Retry with an increasing delay so you don't hammer a struggling server:

async function retry(operation, maxRetries = 3, baseDelay = 300) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt >= maxRetries) throw error;
      const wait = baseDelay * 2 ** attempt + Math.random() * 100; // backoff + jitter
      await new Promise((res) => setTimeout(res, wait));
    }
  }
}

retry(() => fetchWithTimeout('/api/data', 2000))
  .then((data) => console.log('Succeeded:', data))
  .catch((error) => console.error('Gave up:', error.message));

✅ Browser support

All six static methods are supported in every modern browser and current Node.js. allSettled (ES2020) and any (ES2021) are the newest; if you must support very old runtimes, a transpiler like Babel or a small polyfill covers them.

Hands-on Exercise

🏋️ Resilient multi-source loader

Objective: Combine the combinators to load data robustly.

Instructions:

  1. Write loadCritical(sources) that takes an array of fetch functions and returns the first successful result, falling back to a placeholder object if all fail. (Which combinator gives you "first success"?)
  2. Write loadOptional(sources) that attempts all sources and returns an array of only the successful values, silently dropping failures. (Which one never rejects?)
  3. Combine both with a single Promise.all so critical and optional data load together, then log the merged result.
💡 Hint

"First success, fall back if all fail" is Promise.any plus a .catch(). "Attempt all, keep the winners" is Promise.allSettled plus a filter for status === 'fulfilled'.

✅ Solution
function loadCritical(sources) {
  return Promise.any(sources.map((fn) => fn()))
    .catch(() => ({ placeholder: true }));
}

function loadOptional(sources) {
  return Promise.allSettled(sources.map((fn) => fn())).then((results) =>
    results
      .filter((r) => r.status === 'fulfilled')
      .map((r) => r.value)
  );
}

async function loadDashboard(criticalSources, optionalSources) {
  const [critical, optional] = await Promise.all([
    loadCritical(criticalSources),
    loadOptional(optionalSources),
  ]);
  console.log({ critical, optional });
  return { critical, optional };
}

🎯 Quick Quiz

Question 1: You call Promise.all with three fetches and one rejects. What does all do?

Question 2: Which method resolves with the first Promise to fulfill, ignoring earlier rejections?

Question 3: You need results from a batch of operations even if some fail. Which combinator fits best?

Summary & Quiz

🎉 Key Takeaways

  • Promise.resolve / Promise.reject create pre-settled Promises for fast paths and early exits.
  • Promise.all waits for all to fulfill and is fail-fast — one rejection sinks the batch.
  • Promise.race adopts the first to settle (even a rejection) — ideal for timeouts.
  • Promise.allSettled never rejects and reports every outcome; Promise.any takes the first fulfillment.
  • Real apps combine these for concurrency limits, retries with backoff, and resilient loading.

📚 Further Reading

🚀 What's Next?

You now command the whole Promise toolbox. Next we build on it with async function fundamentals — the async/await syntax that makes Promise-based code read like ordinary sequential steps.

🎉 Toolbox complete!

With the combinators mastered, async/await will feel like a natural next step.