DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

JavaScript Promises: All, AllSettled, Any, Race, and Concurrency Patterns

Promises are central to modern JavaScript, but most developers use only the basics. Firefox fully implements the Promise specification and the full Promise static API. This guide covers practical patterns: error handling, concurrency control, cancellation, and the less-used Promise methods.

Promise Fundamentals Recap

// Creating a Promise
const p = new Promise((resolve, reject) => {
  // Async operation
  setTimeout(() => {
    if (success) resolve(value);
    else reject(new Error('failed'));
  }, 1000);
});

// Consuming
p.then(value => console.log(value))
 .catch(err => console.error(err))
 .finally(() => cleanup());

// Async/await style (same thing, nicer syntax)
try {
  const value = await p;
} catch (err) {
  console.error(err);
} finally {
  cleanup();
}
Enter fullscreen mode Exit fullscreen mode

Promise.all vs Promise.allSettled vs Promise.any vs Promise.race

const p1 = fetch('/api/users');
const p2 = fetch('/api/posts');
const p3 = fetch('/api/comments');

// Promise.all: wait for ALL to succeed, fail fast on any rejection
// Returns: array of results in same order
// Use when: all requests MUST succeed
const [users, posts, comments] = await Promise.all([p1, p2, p3]);
Enter fullscreen mode Exit fullscreen mode
// Promise.allSettled: wait for ALL to settle (succeed or fail)
// Returns: array of {status: 'fulfilled'|'rejected', value|reason}
// Use when: some can fail, you want all results
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('Success:', result.value);
  } else {
    console.error('Failed:', result.reason);
  }
});
Enter fullscreen mode Exit fullscreen mode
// Promise.any: first to SUCCEED wins, fails only if ALL fail
// Returns: first fulfilled value
// Use when: trying multiple sources, use first available
const fastResponse = await Promise.any([
  fetch('https://cdn1.example.com/data.json'),
  fetch('https://cdn2.example.com/data.json'),
  fetch('https://cdn3.example.com/data.json')
]);
Enter fullscreen mode Exit fullscreen mode
// Promise.race: first to settle (success OR failure) wins
// Returns: first settled value/reason
// Use when: timeout patterns
const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error('Timeout')), 5000)
);

const result = await Promise.race([fetch('/api/slow'), timeout]);
Enter fullscreen mode Exit fullscreen mode

Controlled Concurrency

Running 20 requests simultaneously is usually a bad idea. Control concurrency:

async function mapWithConcurrency(items, asyncFn, concurrency = 5) {
  const results = new Array(items.length);
  let index = 0;

  async function worker() {
    while (index < items.length) {
      const currentIndex = index++;
      results[currentIndex] = await asyncFn(items[currentIndex], currentIndex);
    }
  }

  // Run 'concurrency' workers in parallel
  await Promise.all(
    Array.from({ length: Math.min(concurrency, items.length) }, worker)
  );

  return results;
}

// Process 100 items, max 5 at a time
const processed = await mapWithConcurrency(items, async (item) => {
  return await processItem(item);
}, 5);
Enter fullscreen mode Exit fullscreen mode

Error Handling Patterns

// Pattern 1: Centralized error handling
async function safeRequest(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return [await response.json(), null]; // [data, error]
  } catch (err) {
    return [null, err]; // [data, error]
  }
}

// Usage: no try/catch at call sites
const [user, error] = await safeRequest('/api/user/123');
if (error) { handleError(error); return; }
console.log(user);

// Pattern 2: Promise utilities
function withTimeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
    )
  ]);
}

function withRetry(fn, attempts = 3, delay = 1000) {
  return async function(...args) {
    for (let i = 0; i < attempts; i++) {
      try {
        return await fn(...args);
      } catch (err) {
        if (i === attempts - 1) throw err;
        await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
      }
    }
  };
}

const fetchWithRetry = withRetry(fetch, 3, 500);
const data = await fetchWithRetry('/api/unreliable');
Enter fullscreen mode Exit fullscreen mode

Cancellable Promises with AbortController

Promises themselves aren't cancellable, but you can pass a signal:

function cancellableDelay(ms, signal) {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) { reject(new DOMException('Aborted', 'AbortError')); return; }

    const timer = setTimeout(resolve, ms);
    signal?.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new DOMException('Aborted', 'AbortError'));
    });
  });
}

// Usage
const controller = new AbortController();
cancellableDelay(5000, controller.signal)
  .then(() => console.log('delay complete'))
  .catch(err => {
    if (err.name === 'AbortError') console.log('cancelled');
  });

controller.abort(); // Cancel the delay
Enter fullscreen mode Exit fullscreen mode

Promise.withResolvers (Firefox 119+)

A new static method that returns { promise, resolve, reject }:

// Old pattern (verbose)
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });

// New pattern
const { promise, resolve, reject } = Promise.withResolvers();

// Useful for: passing resolve/reject to other code
class TaskQueue {
  constructor() {
    this.queue = [];
  }

  add(task) {
    const { promise, resolve, reject } = Promise.withResolvers();
    this.queue.push({ task, resolve, reject });
    this.processNext();
    return promise;
  }

  async processNext() {
    if (this.queue.length === 0) return;
    const { task, resolve, reject } = this.queue.shift();
    try {
      resolve(await task());
    } catch (err) {
      reject(err);
    }
    this.processNext();
  }
}
Enter fullscreen mode Exit fullscreen mode

Deduplicating Concurrent Requests

When multiple parts of your app might request the same data simultaneously:

const inFlight = new Map();

async function deduplicateFetch(url) {
  if (inFlight.has(url)) {
    return inFlight.get(url); // Return the same promise
  }

  const promise = fetch(url)
    .then(r => r.json())
    .finally(() => inFlight.delete(url));

  inFlight.set(url, promise);
  return promise;
}

// All these calls get the SAME promise (one network request)
const [a, b, c] = await Promise.all([
  deduplicateFetch('/api/config'),
  deduplicateFetch('/api/config'),
  deduplicateFetch('/api/config')
]);
Enter fullscreen mode Exit fullscreen mode

Sequential vs. Parallel Execution

// SEQUENTIAL (each waits for previous): 3 seconds total
const user = await fetch('/api/user');
const posts = await fetch(`/api/posts?userId=${user.id}`);

// PARALLEL (all run simultaneously): ~1 second total
const [user, topPosts] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/top-posts').then(r => r.json())
]);

// When to use sequential: when result of one request is needed for the next
// When to use parallel: when requests are independent
Enter fullscreen mode Exit fullscreen mode

Testing Promises in Firefox DevTools

Firefox's DevTools console supports top-level await for testing:

// In console:
const data = await fetch('/api/users').then(r => r.json());
data.map(u => u.name)
Enter fullscreen mode Exit fullscreen mode

The console shows the resolved value of awaited promises directly, without .then() handlers.

Top comments (0)