Learning Objectives

  • Understand Promise.any() behavior
  • Learn the difference from Promise.race()
  • Handle AggregateError
  • Implement fallback strategies

What is Promise.any()?

Promise.any() returns a Promise that fulfills as soon as any of the input Promises fulfills. It only rejects if all Promises reject.

Promise.any([promise1, promise2, promise3])
  .then(result => {
    // First successful result
  })
  .catch(error => {
    // AggregateError if ALL failed
  });

Basic Example

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

Promise.any(promises)
  .then(result => {
    console.log(result); // "Success!"
  })
  .catch(error => {
    console.error('All failed');
  });

Promise.any() vs Promise.race()

Feature Promise.any() Promise.race()
Resolves when First fulfillment First settlement (fulfill or reject)
Rejects when All reject First rejection
Ignores Rejections (until all fail) Nothing
Use case Fallback sources Fastest response

Common Use Cases

1. Fallback API Endpoints

async function fetchFromMultipleSources(endpoint) {
  const sources = [
    fetch(`https://api1.example.com${endpoint}`),
    fetch(`https://api2.example.com${endpoint}`),
    fetch(`https://api3.example.com${endpoint}`)
  ];
  
  try {
    const response = await Promise.any(sources);
    return await response.json();
  } catch (error) {
    throw new Error('All API sources failed');
  }
}

fetchFromMultipleSources('/data')
  .then(data => console.log('Got data:', data))
  .catch(error => console.error(error));

2. CDN Fallbacks

function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.onload = () => resolve(src);
    script.onerror = () => reject(new Error(`Failed: ${src}`));
    script.src = src;
    document.head.appendChild(script);
  });
}

const cdns = [
  'https://cdn1.example.com/library.js',
  'https://cdn2.example.com/library.js',
  'https://cdn3.example.com/library.js'
];

Promise.any(cdns.map(loadScript))
  .then(src => console.log('Loaded from:', src))
  .catch(() => console.error('All CDNs failed'));

3. Database Replicas

async function queryFromReplicas(query) {
  const replicas = [
    queryDatabase('primary', query),
    queryDatabase('replica1', query),
    queryDatabase('replica2', query)
  ];
  
  return Promise.any(replicas);
}

queryFromReplicas('SELECT * FROM users')
  .then(results => console.log('Query results:', results))
  .catch(error => console.error('All replicas failed'));

AggregateError

When all Promises reject, Promise.any() throws an AggregateError containing all rejection reasons:

const promises = [
  Promise.reject(new Error('Error 1')),
  Promise.reject(new Error('Error 2')),
  Promise.reject(new Error('Error 3'))
];

Promise.any(promises)
  .catch(error => {
    console.log(error instanceof AggregateError); // true
    console.log(error.errors); // Array of all errors
    
    error.errors.forEach((err, index) => {
      console.log(`Promise ${index + 1}:`, err.message);
    });
  });

Handling AggregateError

async function fetchWithFallbacks(urls) {
  try {
    const response = await Promise.any(
      urls.map(url => fetch(url))
    );
    return await response.json();
  } catch (error) {
    if (error instanceof AggregateError) {
      console.error('All sources failed:');
      error.errors.forEach((err, i) => {
        console.error(`  Source ${i + 1}:`, err.message);
      });
    }
    throw new Error('Failed to fetch from any source');
  }
}

Real-World Example: Image Loading with Fallbacks

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error(`Failed: ${url}`));
    img.src = url;
  });
}

async function loadImageWithFallbacks(imageName) {
  const sources = [
    `https://cdn1.example.com/images/${imageName}`,
    `https://cdn2.example.com/images/${imageName}`,
    `https://cdn3.example.com/images/${imageName}`,
    `/local/images/${imageName}` // Local fallback
  ];
  
  try {
    const img = await Promise.any(sources.map(loadImage));
    document.body.appendChild(img);
    return img;
  } catch (error) {
    console.error('All image sources failed');
    // Show placeholder
    const placeholder = document.createElement('div');
    placeholder.textContent = 'Image not available';
    document.body.appendChild(placeholder);
  }
}

loadImageWithFallbacks('photo.jpg');

Combining with Other Methods

Promise.any() + Timeout

function timeout(ms) {
  return new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
}

async function fetchWithFallbacksAndTimeout(urls, timeoutMs) {
  const fetchPromises = urls.map(url => 
    Promise.race([
      fetch(url),
      timeout(timeoutMs)
    ])
  );
  
  return Promise.any(fetchPromises);
}

fetchWithFallbacksAndTimeout([
  'https://slow-api.com/data',
  'https://fast-api.com/data'
], 5000)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('All attempts failed or timed out'));

Polyfill for Older Browsers

if (!Promise.any) {
  Promise.any = function(promises) {
    return new Promise((resolve, reject) => {
      const errors = [];
      let rejectedCount = 0;
      
      promises.forEach((promise, index) => {
        Promise.resolve(promise)
          .then(resolve)
          .catch(error => {
            errors[index] = error;
            rejectedCount++;
            
            if (rejectedCount === promises.length) {
              reject(new AggregateError(
                errors,
                'All promises were rejected'
              ));
            }
          });
      });
    });
  };
}

Best Practices

  1. Use for redundancy - Multiple sources where any success is acceptable
  2. Order matters - Put preferred sources first for better performance
  3. Handle AggregateError - Always check for all failures
  4. Combine with timeouts - Prevent slow sources from delaying results
  5. Log failures - Track which sources are failing for monitoring

Comparison Summary

// Promise.all() - All must succeed
const [a, b, c] = await Promise.all([p1, p2, p3]);
// Rejects if ANY fails

// Promise.allSettled() - Wait for all, get all results
const results = await Promise.allSettled([p1, p2, p3]);
// Never rejects, returns status for each

// Promise.race() - First to settle wins
const result = await Promise.race([p1, p2, p3]);
// Settles with first (success or failure)

// Promise.any() - First success wins
const result = await Promise.any([p1, p2, p3]);
// Rejects only if ALL fail

Key Takeaways

  • Promise.any() resolves with first success
  • ✅ Ignores rejections until all fail
  • ✅ Throws AggregateError when all reject
  • ✅ Perfect for fallback strategies
  • ✅ Different from Promise.race() - ignores rejections
  • ✅ Great for redundant data sources

Next Steps

Now that you've mastered all Promise static methods, let's move on to async/await!