深入浅出理解 Promise.any()

深入浅出理解 Promise.any()

Promise 一直是我们处理 JavaScript 中异步代码的首选工具。如果你曾使用过 Promise.all() 或 Promise.race() 来协调异步操作,应该对这些模式很熟悉。但是,如果只关心第一个成功的结果,而忽略失败的情况呢?这正是 Promise.any() 所做的:它会在第一个 Promise 兑现(fulfill)时立即兑现,并忽略所有拒绝(reject)的 Promise(除非所有的都拒绝)。

运行方式Promise.any(iterable)

  • 接受一个包含 Promise 的可迭代对象。
  • 只要其中一个 Promise 兑现,它就会立即兑现。
  • 如果传入一个空的可迭代对象,它会立即拒绝(reject),并抛出一个 .errors 数组为空的 AggregateError(聚合错误)。

性能提示:传递给 Promise.any() 的 Promise 已经在运行。当其中一个成功时,它不会取消其余的 Promise。如果取消操作很重要,请使用 AbortController

日常应用场景

  1. 后备 API (Fallback APIs)

    想象一下你正在调用多个第三方接口,只关心第一个能正常工作的接口:

    const fetchWithCheck = (url) => fetch(url)
      .then(response => {
        if (!response.ok) {
          thrownewError(`HTTP 错误: ${response.status}`);
        }
        return response;
      });

    Promise.any([
      fetchWithCheck('https://mirror1.example.com/data'),
      fetchWithCheck('https://mirror2.example.com/data'),
      fetchWithCheck('https://mirror3.example.com/data')
    ])
      .then(response => response.json())
      .then(data => {
        console.log('从第一个响应的服务器获取数据:', data);
      })
      .catch(error => {
        if (error instanceof AggregateError) {
          console.error('所有服务器都失败了:', error.errors);
        } else {
          console.error('意外错误:', error);
        }
      });

    ⚠️ 注意:fetch() 仅在发生网络错误(例如离线)或违反 CORS 策略(表现为通用网络故障)时才会拒绝(reject)。

  2. 渐进式增强与可选功能 (Progressive enhancement with optional features)

    你正在构建一个尝试使用多种浏览器 API 的功能。只要其中任何一个 API 成功,该功能就能工作:

    const tryClipboardAPI = () => navigator.clipboard.readText()
      .then(text =>`剪贴板内容: ${text}`);

    const tryLegacyPrompt = () =>newPromise((resolve, reject) => {
    const text = prompt('请粘贴您的数据:');
    if (text === null) {
        reject(newError('用户取消了提示'));
      } else {
        resolve(`提示框输入: ${text}`);
      }
    });

    Promise.any([
      tryClipboardAPI(),
      tryLegacyPrompt()
    ])
      .then(result =>console.log('获取到用户输入:', result))
      .catch(error => {
        if (error instanceof AggregateError) {
          console.error('无可用输入方法:', error.errors);
        } else {
          console.error('意外错误:', error);
        }
      });

    Clipboard API 通常需要明确的用户交互和权限。如果在非用户手势上下文(如点击事件)中调用,它也可能会拒绝。使用像 prompt() 这样的后备方案可以确保您的功能在更广泛的浏览器和权限场景下仍然可用。使用这种模式可以使您的功能更加灵活和用户友好,即使在缺乏现代 API 的旧浏览器中也是如此。

要点 (Remember these gotchas)

  • Promise.any() 会随着第一个兑现的 Promise 而兑现,并忽略其余的 Promise,即使它们之后会拒绝。
  • 如果所有的 Promise 都拒绝,它会拒绝并抛出一个 AggregateError(聚合错误),该错误的 .errors 属性数组包含了所有拒绝的原因。
  • 不会中止剩余的 Promise。如果需要取消操作,请使用 AbortController
  • 在调用 Promise.any() 时,Promise 已经开始运行。它不会延迟执行。
  • 传入空的可迭代对象会导致 Promise.any() 立即拒绝,并抛出一个 .errors 数组为空的 AggregateError

示例:处理 AggregateError

Promise.any([
  Promise.reject(new Error('错误 A')),
  Promise.reject(new Error('错误 B'))
])
.catch(err => {
  console.log(err instanceof AggregateError); // true
  console.log(err.errors.map(e => e.message)); // ['错误 A', '错误 B']
});

比较 Promise.any() 与其他方法 (Comparing Promise.any() with the rest)

方法 (Method)
兑现时机 (Resolves when…)
拒绝时机 (Rejects when…)
说明 (Notes)
Promise.all()
✅ 所有 Promise 都兑现
❌❌ 任一 Promise 拒绝
当您需要所有结果时使用。
Promise.any()
🟢🟢🟢 第一个 Promise 兑现
🔴🔴 所有 Promise 都拒绝
当任何一次成功就足够时
最佳。
Promise.race()
🏁🏁🏁 第一个 Promise 敲定(无论成功失败)
⚠⚠⚠️ 同上 (Same)
返回第一个敲定的结果(成功或失败)。
Promise.allSettled()
📦📦 所有 Promise 都敲定(完成或拒绝)
🚫🚫🚫 永不拒绝(总是兑现)
提供所有 Promise 的最终状态(兑现或拒绝)。

浏览器支持 (Browser support)Promise.any() 在所有现代浏览器(Chrome 85+、Firefox 79+、Safari 14+、Edge 85+)和 Node.js 15+ 中都得到支持。对于旧版环境,您可以使用像 https://github.com/zloirock/core-js 这样的 polyfill。

何时不应使用 Promise.any() (When not to use Promise.any())当你的目标是至少获得一次成功,并且允许一些 Promise 失败时,请使用 Promise.any()。但在以下情况下应避免使用:

  • 需要所有结果 → 使用 Promise.all()
  • 想要第一个敲定的结果(无论成功或失败)→ 使用 Promise.race()
  • 需要单独处理每个结果 → 使用 Promise.allSettled() 或手动 map() + .catch() 处理。

优先成功的异步操作实践 (Success-first async in action)Promise.any() 是一个现代的、优雅的解决方案,适用于至少一次成功就足够,并且预期会存在失败的场景。无论是在优化 API 调用,还是通过可选功能增强用户体验,它都是异步工具箱中的一个强大补充。将它应用到你的下一个异步工作流中,尤其是在预期会有部分失败的地方,让你的成功来自于最先响应的那个来源。


最后

还没有使用过我们刷题网站(https://fe.ecool.fun/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库已经更新1600多道面试题,除了八股文,还有现在面试官青睐的场景题,甚至最热的AI与前端相关的面试题已经更新,努力做全网最全最新的前端刷题网站。


有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。

图片