阿里云主机折上折
  • 微信号
Current Site:Index > Promise.any()

Promise.any()

Author:Chuan Chen 阅读数:10324人阅读 分类: JavaScript

Basic Concept of Promise.any()

Promise.any() is a new method introduced in ECMAScript 2021 (ES12) for handling multiple Promise instances. It takes an iterable collection of Promise objects and returns a new Promise. As soon as any one of the Promises is fulfilled, this new Promise resolves immediately with the value of the first successful Promise. If all Promises are rejected, the returned Promise is rejected with an AggregateError object containing all the rejection reasons.

const promise1 = Promise.reject("Error 1");
const promise2 = Promise.resolve("Success 2");
const promise3 = new Promise((resolve) => setTimeout(resolve, 100, "Success 3"));

Promise.any([promise1, promise2, promise3])
  .then((value) => console.log(value)) // Output: "Success 2"
  .catch((error) => console.error(error));

Differences from Promise.all() and Promise.race()

Promise.any() differs significantly from Promise.all() and Promise.race():

  1. Promise.all(): Waits for all Promises to succeed or the first failure.
  2. Promise.race(): Adopts the first settled Promise (whether fulfilled or rejected).
  3. Promise.any(): Adopts the first fulfilled Promise and only rejects if all fail.
// Promise.all() example
Promise.all([Promise.resolve(1), Promise.resolve(2)])
  .then(values => console.log(values)); // [1, 2]

// Promise.race() example
Promise.race([Promise.reject("Error"), Promise.resolve("Success")])
  .catch(err => console.log(err)); // "Error"

// Promise.any() example
Promise.any([Promise.reject("Error"), Promise.resolve("Success")])
  .then(value => console.log(value)); // "Success"

Error Handling Mechanism

When all passed Promises are rejected, Promise.any() throws an AggregateError object, which includes an errors property containing an array of all rejection reasons.

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

Promise.any(promises)
  .catch(error => {
    console.log(error instanceof AggregateError); // true
    console.log(error.errors); // ["Error 1", "Error 2", "Error 3"]
  });

Practical Use Cases

Multi-Source Data Fetching

When fetching the same data from multiple sources, Promise.any() can be used to get the fastest response:

const fetchFromSource1 = fetch('https://source1.com/data');
const fetchFromSource2 = fetch('https://source2.com/data');
const fetchFromSource3 = fetch('https://source3.com/data');

Promise.any([fetchFromSource1, fetchFromSource2, fetchFromSource3])
  .then(response => response.json())
  .then(data => console.log('Received data:', data))
  .catch(error => console.error('All sources failed:', error.errors));

Fallback Service Calls

In a microservices architecture, Promise.any() can be used to implement service degradation:

async function getServiceData() {
  const primaryService = callPrimaryService().catch(() => { throw "Primary failed" });
  const secondaryService = callSecondaryService().catch(() => { throw "Secondary failed" });
  const fallbackService = callFallbackService();
  
  return Promise.any([primaryService, secondaryService, fallbackService]);
}

getServiceData()
  .then(data => updateUI(data))
  .catch(error => showError(error));

Browser Compatibility and Polyfill

Browser support for Promise.any():

  • Chrome 85+
  • Firefox 79+
  • Safari 14+
  • Edge 85+
  • Node.js 15.0.0+

For unsupported environments, use the following polyfill:

if (!Promise.any) {
  Promise.any = function(promises) {
    return new Promise((resolve, reject) => {
      let rejections = [];
      let pending = promises.length;
      
      if (pending === 0) {
        reject(new AggregateError([], "All promises were rejected"));
        return;
      }
      
      promises.forEach((promise, index) => {
        Promise.resolve(promise)
          .then(resolve)
          .catch(error => {
            rejections[index] = error;
            pending--;
            
            if (pending === 0) {
              reject(new AggregateError(rejections, "All promises were rejected"));
            }
          });
      });
    });
  };
}

Performance Considerations and Best Practices

When using Promise.any(), note the following:

  1. Canceling pending Promises: Other Promises continue executing after resolution.
  2. Memory consumption: A large number of Promises may consume significant memory.
  3. Error collection: AggregateError may contain extensive error information.

Optimization example:

// Use AbortController to cancel pending requests
async function fetchFastest(urls, signal) {
  const controller = new AbortController();
  signal?.addEventListener('abort', () => controller.abort());
  
  const requests = urls.map(url => 
    fetch(url, { signal: controller.signal })
      .then(response => {
        controller.abort(); // Cancel other requests
        return response;
      })
  );
  
  return Promise.any(requests);
}

// Usage
const controller = new AbortController();
fetchFastest(['url1', 'url2', 'url3'], controller.signal)
  .then(response => console.log(response))
  .catch(error => console.error(error));

// Timeout cancellation
setTimeout(() => controller.abort(), 5000);

Integration with Other Async Patterns

Promise.any() can be combined with other async patterns:

With async/await

async function loadContent(sources) {
  try {
    const responses = sources.map(source => fetch(source));
    const firstResponse = await Promise.any(responses);
    return await firstResponse.json();
  } catch (error) {
    if (error instanceof AggregateError) {
      console.error('All sources failed:', error.errors);
    } else {
      console.error('Unexpected error:', error);
    }
    throw error;
  }
}

With Promise.allSettled()

async function getDataWithFallback(primary, fallbacks) {
  const allPromises = [primary, ...fallbacks];
  const result = await Promise.any(allPromises);
  
  // Log all Promise states
  const settlements = await Promise.allSettled(allPromises);
  logSettlements(settlements);
  
  return result;
}

Special Cases and Edge Scenarios

Empty Array Input

When passed an empty array, Promise.any() rejects synchronously:

Promise.any([])
  .catch(error => {
    console.log(error instanceof AggregateError); // true
    console.log(error.errors); // []
    console.log(error.message); // "All promises were rejected"
  });

Non-Promise Values

Promise.any() converts non-Promise values into resolved Promises:

Promise.any([1, Promise.resolve(2), Promise.reject(3)])
  .then(value => console.log(value)); // 1

Synchronous Errors

If a Promise in the iterable throws an error synchronously:

const promise = new Promise(() => { throw new Error("Sync error"); });

Promise.any([promise])
  .catch(error => {
    console.log(error instanceof AggregateError); // true
    console.log(error.errors[0] instanceof Error); // true
  });

Usage in Node.js

In Node.js, Promise.any() can be used for filesystem operations:

const fs = require('fs').promises;
const path = require('path');

async function findConfigFile(locations) {
  const filePromises = locations.map(location =>
    fs.access(location)
      .then(() => location)
      .catch(() => { throw `${location} not found` })
  );
  
  return Promise.any(filePromises);
}

findConfigFile([
  path.join(process.cwd(), 'config.json'),
  path.join(process.cwd(), 'config.default.json'),
  '/etc/app/config.json'
])
  .then(configPath => console.log('Using config:', configPath))
  .catch(error => console.error('No config found:', error.errors));

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.