Skip to main content
โšก Calmops

Promise Utilities: all, race, allSettled, any in JavaScript

Promise Utilities: all, race, allSettled, any in JavaScript

Introduction

JavaScript provides several utility methods for working with multiple promises simultaneously. These methods allow you to coordinate asynchronous operations, handle multiple concurrent requests, and implement complex async patterns. Understanding when and how to use each utility is crucial for writing efficient asynchronous code.

In this article, you’ll learn about Promise.all(), Promise.race(), Promise.allSettled(), and Promise.any(), including their use cases and practical applications.

Promise.all()

Promise.all() waits for all promises to resolve or for any to reject. It returns a single promise that resolves with an array of results.

Basic Usage

// Basic example
const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve('foo'), 100));
const promise3 = fetch('/api/data').then(r => r.json());

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // [3, 'foo', {...}]
  })
  .catch(error => {
    console.error('One promise failed:', error);
  });

Characteristics

  • Resolves when: All promises resolve
  • Rejects when: Any promise rejects
  • Returns: Array of results in the same order
  • Short-circuits: Stops on first rejection
// All resolve
Promise.all([
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3)
])
.then(results => {
  console.log(results); // [1, 2, 3]
});

// One rejects - entire promise rejects
Promise.all([
  Promise.resolve(1),
  Promise.reject('Error!'),
  Promise.resolve(3)
])
.catch(error => {
  console.log(error); // 'Error!'
});

Practical Examples

Example 1: Fetching Multiple Resources

// Fetch multiple API endpoints
async function fetchUserData(userId) {
  try {
    const [user, posts, comments] = await Promise.all([
      fetch(`/api/users/${userId}`).then(r => r.json()),
      fetch(`/api/users/${userId}/posts`).then(r => r.json()),
      fetch(`/api/users/${userId}/comments`).then(r => r.json())
    ]);
    
    return { user, posts, comments };
  } catch (error) {
    console.error('Failed to fetch user data:', error);
    throw error;
  }
}

// Usage
const userData = await fetchUserData(1);
console.log(userData);

Example 2: Parallel Processing

// Process multiple items in parallel
async function processItems(items) {
  const promises = items.map(item => processItem(item));
  
  try {
    const results = await Promise.all(promises);
    console.log('All items processed:', results);
    return results;
  } catch (error) {
    console.error('Processing failed:', error);
    throw error;
  }
}

async function processItem(item) {
  // Simulate async processing
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(`Processed: ${item}`);
    }, Math.random() * 1000);
  });
}

// Usage
await processItems(['item1', 'item2', 'item3']);

Example 3: Validation

// Validate multiple fields in parallel
async function validateForm(formData) {
  const validations = [
    validateEmail(formData.email),
    validateUsername(formData.username),
    validatePassword(formData.password),
    checkUsernameAvailability(formData.username)
  ];
  
  try {
    const results = await Promise.all(validations);
    return results.every(result => result === true);
  } catch (error) {
    console.error('Validation failed:', error);
    return false;
  }
}

async function validateEmail(email) {
  // Simulate validation
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

async function validateUsername(username) {
  return username.length >= 3;
}

async function validatePassword(password) {
  return password.length >= 8;
}

async function checkUsernameAvailability(username) {
  const response = await fetch(`/api/check-username/${username}`);
  return response.ok;
}

Promise.race()

Promise.race() returns a promise that resolves or rejects as soon as the first promise settles (resolves or rejects).

Basic Usage

// Basic example
const promise1 = new Promise(resolve => setTimeout(() => resolve('first'), 500));
const promise2 = new Promise(resolve => setTimeout(() => resolve('second'), 100));

Promise.race([promise1, promise2])
  .then(result => {
    console.log(result); // 'second' (fastest)
  });

Characteristics

  • Resolves when: First promise resolves
  • Rejects when: First promise rejects
  • Returns: Result of the first settled promise
  • Short-circuits: Stops on first settlement
// First to resolve wins
Promise.race([
  new Promise(resolve => setTimeout(() => resolve('slow'), 1000)),
  new Promise(resolve => setTimeout(() => resolve('fast'), 100))
])
.then(result => {
  console.log(result); // 'fast'
});

// First to reject wins
Promise.race([
  new Promise((_, reject) => setTimeout(() => reject('error'), 100)),
  new Promise(resolve => setTimeout(() => resolve('success'), 1000))
])
.catch(error => {
  console.log(error); // 'error'
});

Practical Examples

Example 1: Timeout Implementation

// Race between fetch and timeout
function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url).then(r => r.json());
  
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Request timeout')), timeout)
  );
  
  return Promise.race([fetchPromise, timeoutPromise]);
}

// Usage
try {
  const data = await fetchWithTimeout('/api/data', 3000);
  console.log(data);
} catch (error) {
  console.error('Request failed:', error.message);
}

Example 2: First Successful Response

// Try multiple APIs, use first successful response
async function fetchFromMultipleSources(urls) {
  const promises = urls.map(url =>
    fetch(url)
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
  );
  
  try {
    const result = await Promise.race(promises);
    console.log('Got response from fastest source');
    return result;
  } catch (error) {
    console.error('All sources failed:', error);
    throw error;
  }
}

// Usage
const data = await fetchFromMultipleSources([
  'https://api1.example.com/data',
  'https://api2.example.com/data',
  'https://api3.example.com/data'
]);

Example 3: User Interaction Timeout

// Wait for user action or timeout
function waitForUserAction(timeout = 30000) {
  const clickPromise = new Promise(resolve => {
    document.addEventListener('click', () => {
      resolve('User clicked');
    }, { once: true });
  });
  
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('User action timeout')), timeout)
  );
  
  return Promise.race([clickPromise, timeoutPromise]);
}

// Usage
try {
  const result = await waitForUserAction(10000);
  console.log(result);
} catch (error) {
  console.log('No user action within timeout');
}

Promise.allSettled()

Promise.allSettled() waits for all promises to settle (resolve or reject) and returns an array of results with their status.

Basic Usage

// Basic example
const promises = [
  Promise.resolve(1),
  Promise.reject('Error'),
  Promise.resolve(3)
];

Promise.allSettled(promises)
  .then(results => {
    console.log(results);
    // [
    //   { status: 'fulfilled', value: 1 },
    //   { status: 'rejected', reason: 'Error' },
    //   { status: 'fulfilled', value: 3 }
    // ]
  });

Characteristics

  • Resolves when: All promises settle (never rejects)
  • Returns: Array of objects with status and value/reason
  • No short-circuit: Waits for all promises
  • Always succeeds: Never rejects
// All settle, never rejects
Promise.allSettled([
  Promise.resolve('success'),
  Promise.reject('failure'),
  Promise.resolve('another success')
])
.then(results => {
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Promise ${index} resolved:`, result.value);
    } else {
      console.log(`Promise ${index} rejected:`, result.reason);
    }
  });
});

Practical Examples

Example 1: Batch Operations with Partial Failures

// Save multiple items, continue even if some fail
async function saveMultipleItems(items) {
  const savePromises = items.map(item => saveItem(item));
  
  const results = await Promise.allSettled(savePromises);
  
  const successful = results.filter(r => r.status === 'fulfilled');
  const failed = results.filter(r => r.status === 'rejected');
  
  console.log(`Saved ${successful.length} items`);
  console.log(`Failed to save ${failed.length} items`);
  
  if (failed.length > 0) {
    console.warn('Failed items:', failed.map(f => f.reason));
  }
  
  return { successful, failed };
}

async function saveItem(item) {
  const response = await fetch('/api/items', {
    method: 'POST',
    body: JSON.stringify(item)
  });
  
  if (!response.ok) {
    throw new Error(`Failed to save: ${response.statusText}`);
  }
  
  return response.json();
}

Example 2: Parallel Requests with Error Tolerance

// Fetch from multiple sources, collect what succeeds
async function fetchFromMultipleSources(urls) {
  const fetchPromises = urls.map(url =>
    fetch(url).then(r => r.json())
  );
  
  const results = await Promise.allSettled(fetchPromises);
  
  const data = results
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);
  
  const errors = results
    .filter(r => r.status === 'rejected')
    .map(r => r.reason);
  
  console.log(`Retrieved ${data.length} successful responses`);
  if (errors.length > 0) {
    console.warn(`${errors.length} requests failed`);
  }
  
  return { data, errors };
}

Example 3: Form Submission with Multiple Validations

// Validate multiple fields, collect all errors
async function validateFormFields(formData) {
  const validations = [
    validateEmail(formData.email),
    validateUsername(formData.username),
    validatePassword(formData.password),
    validateAge(formData.age)
  ];
  
  const results = await Promise.allSettled(validations);
  
  const errors = results
    .map((result, index) => {
      if (result.status === 'rejected') {
        return result.reason;
      }
      return null;
    })
    .filter(error => error !== null);
  
  return {
    isValid: errors.length === 0,
    errors
  };
}

async function validateEmail(email) {
  if (!email.includes('@')) {
    throw new Error('Invalid email format');
  }
  return true;
}

async function validateUsername(username) {
  if (username.length < 3) {
    throw new Error('Username must be at least 3 characters');
  }
  return true;
}

async function validatePassword(password) {
  if (password.length < 8) {
    throw new Error('Password must be at least 8 characters');
  }
  return true;
}

async function validateAge(age) {
  if (age < 18) {
    throw new Error('Must be at least 18 years old');
  }
  return true;
}

Promise.any()

Promise.any() returns a promise that resolves as soon as any promise resolves. It rejects only if all promises reject.

Basic Usage

// Basic example
const promises = [
  Promise.reject('Error 1'),
  Promise.reject('Error 2'),
  Promise.resolve('Success')
];

Promise.any(promises)
  .then(result => {
    console.log(result); // 'Success'
  });

Characteristics

  • Resolves when: First promise resolves
  • Rejects when: All promises reject
  • Returns: Result of first resolved promise
  • Rejection type: AggregateError with all rejection reasons
// First to resolve wins
Promise.any([
  Promise.reject('Error 1'),
  Promise.resolve('Success 1'),
  Promise.resolve('Success 2')
])
.then(result => {
  console.log(result); // 'Success 1'
});

// All reject - AggregateError
Promise.any([
  Promise.reject('Error 1'),
  Promise.reject('Error 2'),
  Promise.reject('Error 3')
])
.catch(error => {
  console.log(error.errors); // ['Error 1', 'Error 2', 'Error 3']
});

Practical Examples

Example 1: Multiple API Endpoints

// Try multiple endpoints, use first successful
async function fetchData() {
  const endpoints = [
    '/api/v1/data',
    '/api/v2/data',
    '/api/v3/data'
  ];
  
  const fetchPromises = endpoints.map(endpoint =>
    fetch(endpoint)
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
  );
  
  try {
    const data = await Promise.any(fetchPromises);
    console.log('Got data from first successful endpoint');
    return data;
  } catch (error) {
    console.error('All endpoints failed:', error.errors);
    throw error;
  }
}

Example 2: Redundant Services

// Use first available service
async function getServiceData() {
  const services = [
    fetchFromService1(),
    fetchFromService2(),
    fetchFromService3()
  ];
  
  try {
    const result = await Promise.any(services);
    console.log('Using first available service');
    return result;
  } catch (error) {
    console.error('All services unavailable');
    throw error;
  }
}

async function fetchFromService1() {
  // Simulate service call
  return new Promise((resolve, reject) => {
    setTimeout(() => reject('Service 1 down'), 1000);
  });
}

async function fetchFromService2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve('Data from Service 2'), 500);
  });
}

async function fetchFromService3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve('Data from Service 3'), 2000);
  });
}

Comparison Table

Method Resolves When Rejects When Returns
all() All resolve Any rejects Array of values
race() First settles First rejects First result
allSettled() All settle Never Array of {status, value/reason}
any() First resolves All reject First value

Practical Comparison Examples

// Compare all methods with same promises
const promises = [
  new Promise(resolve => setTimeout(() => resolve('A'), 100)),
  new Promise(resolve => setTimeout(() => resolve('B'), 200)),
  new Promise((_, reject) => setTimeout(() => reject('Error'), 150))
];

// Promise.all() - Rejects on first rejection
Promise.all(promises).catch(e => console.log('all:', e)); // 'Error'

// Promise.race() - Resolves with first result
Promise.race(promises).then(r => console.log('race:', r)); // 'A'

// Promise.allSettled() - All settle
Promise.allSettled(promises).then(r => console.log('allSettled:', r));
// [
//   { status: 'fulfilled', value: 'A' },
//   { status: 'fulfilled', value: 'B' },
//   { status: 'rejected', reason: 'Error' }
// ]

// Promise.any() - First to resolve
Promise.any(promises).then(r => console.log('any:', r)); // 'A'

Common Mistakes to Avoid

Mistake 1: Using Promise.all() When You Need Error Tolerance

// โŒ Wrong - Fails if any request fails
const results = await Promise.all([
  fetch('/api/1').then(r => r.json()),
  fetch('/api/2').then(r => r.json()),
  fetch('/api/3').then(r => r.json())
]);

// โœ… Correct - Use allSettled for error tolerance
const results = await Promise.allSettled([
  fetch('/api/1').then(r => r.json()),
  fetch('/api/2').then(r => r.json()),
  fetch('/api/3').then(r => r.json())
]);

Mistake 2: Not Handling AggregateError from Promise.any()

// โŒ Wrong - Doesn't handle AggregateError properly
Promise.any(promises).catch(error => {
  console.log(error.message); // Doesn't show individual errors
});

// โœ… Correct - Access errors array
Promise.any(promises).catch(error => {
  console.log('All failed:', error.errors);
});

Mistake 3: Using Promise.race() for Timeout Without Proper Cleanup

// โŒ Wrong - Doesn't clean up the slower promise
Promise.race([
  fetch('/api/data').then(r => r.json()),
  new Promise((_, reject) => setTimeout(() => reject('Timeout'), 5000))
]);

// โœ… Correct - Use AbortController for proper cleanup
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

Promise.race([
  fetch('/api/data', { signal: controller.signal }).then(r => r.json()),
  new Promise((_, reject) => setTimeout(() => reject('Timeout'), 5000))
])
.finally(() => clearTimeout(timeout));

Summary

Promise utilities enable powerful async patterns:

  • Promise.all() - Wait for all promises, fail on first rejection
  • Promise.race() - Use first settled promise
  • Promise.allSettled() - Wait for all, collect results and errors
  • Promise.any() - Use first resolved promise, fail only if all reject
  • Choose the right utility based on your use case
  • Handle errors appropriately for each utility
  • Use AbortController for proper cleanup with race()

Next Steps

Continue your learning journey:

Comments