Promise.any 的竞速成功处理

在 JavaScript 的异步编程中,Promise 一直是处理异步操作的核心机制。随着 ECMAScript 标准的不断演进,ES2021 (ES12) 引入了 Promise.any() 方法,为 Promise 处理模式增添了新的维度。本文将深入探讨 Promise.any() 的工作原理、使用场景以及它与现有 Promise 组合方法的区别。

Promise.any 的基本概念

Promise.any() 是一个静态方法,它接收一个 Promise 可迭代对象(如数组),并返回一个新的 Promise:

javascript 复制代码
Promise.any(iterable);

其核心行为是"竞速成功"(short-circuits when any promise fulfills):

  • 只要有一个 Promise 成功(fulfilled),就立即返回该成功值
  • 只有当所有 Promise 都失败(rejected)时,才会返回一个包含所有拒绝原因的 AggregateError

与现有 Promise 方法的对比

为了更好地理解 Promise.any(),让我们将其与其他 Promise 组合方法进行比较:

方法 描述 短路条件
Promise.all 所有成功或一个失败 第一个拒绝
Promise.race 第一个敲定(无论成功或失败) 第一个敲定
Promise.allSettled 等待所有敲定,不短路 不短路
Promise.any 第一个成功或全部失败 第一个成功

使用示例

基本用法

javascript 复制代码
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

Promise.any([promise1, promise2, promise3])
  .then((value) => console.log(value)); // 输出: "quick"

全部失败的情况

javascript 复制代码
const promises = [
  Promise.reject('Error 1'),
  Promise.reject('Error 2'),
  Promise.reject('Error 3')
];

Promise.any(promises)
  .catch((error) => {
    console.log(error.errors); // 输出: ['Error 1', 'Error 2', 'Error 3']
  });

实际应用场景

  1. 多源数据获取:从多个备用API获取数据,只要有一个成功即可

    javascript 复制代码
    const apis = [
      fetch('https://api.backup1.com/data'),
      fetch('https://api.backup2.com/data'),
      fetch('https://api.backup3.com/data')
    ];
    
    Promise.any(apis)
      .then(response => processData(response))
      .catch(allErrors => logErrors(allErrors));
  2. 性能优化:从最快的CDN加载资源

    javascript 复制代码
    const cdnUrls = [
      'https://cdn1.example.com/library.js',
      'https://cdn2.example.com/library.js',
      'https://cdn3.example.com/library.js'
    ];
    
    Promise.any(cdnUrls.map(url => fetch(url)))
      .then(response => loadScript(response.url));
  3. 冗余操作:执行多个相同的操作以确保至少一个成功

    javascript 复制代码
    const databaseWrites = [
      writeToPrimaryDatabase(data),
      writeToSecondaryDatabase(data)
    ];
    
    Promise.any(databaseWrites)
      .then(() => console.log('Data persisted successfully'));

错误处理

当所有 Promise 都拒绝时,Promise.any() 会抛出一个 AggregateError,这是一个新的错误类型,包含了所有拒绝原因:

javascript 复制代码
try {
  await Promise.any(rejectedPromises);
} catch (error) {
  if (error instanceof AggregateError) {
    console.error('All promises failed:', error.errors);
  } else {
    console.error('Unexpected error:', error);
  }
}

浏览器兼容性与polyfill

截至知识截止日期,Promise.any() 已在现代浏览器中实现,但对于旧环境,可以使用以下polyfill:

javascript 复制代码
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"));
      }
      
      promises.forEach(promise => {
        Promise.resolve(promise)
          .then(resolve)
          .catch(reason => {
            rejections.push(reason);
            pending--;
            if (pending === 0) {
              reject(new AggregateError(rejections, "All promises were rejected"));
            }
          });
      });
    });
  };
}

总结

Promise.any() 是 ES2021 引入的一个强大工具,它填补了 JavaScript Promise API 的空白,为"竞速成功"场景提供了原生支持。通过理解其与 Promise.all()Promise.race()Promise.allSettled() 的区别,开发者可以更精确地选择适合不同异步场景的 Promise 组合方法。

在实际应用中,Promise.any() 特别适合需要冗余执行、备用方案或优化响应时间的场景。随着 JavaScript 异步编程的不断发展,掌握这些工具将使开发者能够编写更健壮、更高效的异步代码。