Promise.allSettled() 和 Promise.any() 有什么问题?Promise.allSettled Promise.any 命名不一致 吞下拒绝 结论

2025-06-07

Promise.allSettled() 和 Promise.any() 有什么问题?

Promise.allSettled

Promise.any

不一致

命名

接受拒绝

结论

我最近在 v8 博客上读了一篇关于Promise 组合器的文章。文章讲的是 Promise API 中即将推出的两个方法:Promise.allSettled()Promise.any()。我感到很沮丧。这些方法的设计在我看来与当前的 Promise API 不一致。下面我来分享一下我的看法。

Promise.allSettled

根据文章: 

Promise.allSettled当所有输入的承诺都得到解决时,会向您发出信号,这意味着它们要么被实现,要么被拒绝。

用例是发送几个 API 调用并等待全部完成:

const promises = [
  fetch('/api-call-1'),
  fetch('/api-call-2'),
  fetch('/api-call-3'),
];

await Promise.allSettled(promises);

removeLoadingIndicator();
Enter fullscreen mode Exit fullscreen mode

当然,这很有用。但这个任务可以用 .map()和轻松解决Promise.all()。改动很小:

const promises = [
  fetch('/api-call-1'),
  fetch('/api-call-2'),
  fetch('/api-call-3'),
].map(p => p.catch(e => e)); // <-- the only change

await Promise.all(promises);

removeLoadingIndicator();
Enter fullscreen mode Exit fullscreen mode

是否值得实现一个只需几行代码就能解决的新核心方法?对我来说,这是一个库级功能,而不是核心 API 方法。

但更重要的是,它Promise.allSettled带来了额外的抽象,增加了代码复杂度。与此不同,Promise.all它使用包装对象数组{status, reason}而不是纯 Promise 值来实现。作为一名开发者,我不喜欢它。我期望名称相似的方法 .all()/.allSettled()能够以类似的方式运行。但事实并非如此。

此外,这种代码Promise.allSettled会导致更糟糕的错误控制。错误应该从最终结果中过滤掉,而不是像传统那样在 catch 块中处理。这反过来又有以下缺点:

  • 错误发生时不会立即处理。如果出现多个相关错误,您无法确定哪个是原始错误。日志中会包含错误的时间戳。
  • 如果至少有一个承诺永远处于待决状态,则不会处理错误。

目前的方法Promise.all不允许这样的事情发生。

Promise.any

Promise.any一旦其中一个承诺实现,就会向您发出信号。

换句话说Promise.any就是Promise.race忽略拒绝。

用例是检查多个端点并从第一个成功的端点获取数据:

const promises = [
  fetch('/endpoint-a').then(() => 'a'),
  fetch('/endpoint-b').then(() => 'b'),
  fetch('/endpoint-c').then(() => 'c'),
];
try {
  const first = await Promise.any(promises);
} catch (error) {
  // All of the promises were rejected.
  console.log(error);
}
Enter fullscreen mode Exit fullscreen mode

我同意有时它可能有用。但频率是多少?在多少个项目中,你使用过这种模式,向相同的端点发出多个并行请求来获取相同的数据?欢迎在评论中分享。但在我看来,这种情况并不常见。如果社区能获得Bluebird 的 原生实现,岂不是更有用Promise.each()Promise.delay()

此外,Promise.any还引入了一种新的错误类型——错误AggregateError。如果所有 Promise 都被拒绝,此类错误会包含指向其他错误的链接。这又是一种错误处理方式!它不同于从Promise.allSettled成功结果中提取错误的方式,也不同于Promise.all/Promise.race仅使用Error实例的拒绝方式。如果每个新的 Promise API 方法都引入新的错误处理方式,JavaScript 将会变成什么样?虽然该提案尚处于非常早期的阶段,但我对其发展方向仍心存疑虑。

基于当前的 Promise API,实现Promise.any有点棘手,但实际上只有两行代码

const reverse = p => new Promise((resolve, reject) => Promise.resolve(p).then(reject, resolve));
Promise.any = arr => reverse(Promise.all(arr.map(reverse)));
Enter fullscreen mode Exit fullscreen mode

我们难道不应该把它留在库中并保持核心 Promise API 干净、简单吗?

不一致

为啥Promise.allPromise.race这么漂亮?

因为它们的行为非常一致,与普通的 Promise 类似:仅用一个值即可完成,仅用一个错误即可拒绝。无需包装值,无需累积错误,也无需额外的复杂性。

为什么Promise.allSettledPromise.any对我来说这么奇怪?

  • Promise.allSettled执行时返回一个包含状态和原因的对象数组,其中包含底层的 Promise 值。拒绝时……永远不会。
  • Promise.any使用单一值完成,并忽略中间的拒绝。只有当所有 Promise 都被拒绝时,它才会使用包含所有底层原因的累积原因来拒绝。

这些新方法确实很难理解,因为它们与现有的 Promise API 有很大不同。

我预测2020年一个热门的求职面试问题:
这四种方法有什么区别?

  1. Promise.all()
  2. Promise.allSettled()
  3. Promise.race()
  4. Promise.any()

虽然这是一个很酷的问题,但我不认为核心 API 应该鼓励这种复杂性。

命名

我对命名也感到失望。四个行为略有不同的方法应该有一个非常清晰的名称。否则,每次在代码中遇到它们,我都得重新检查MDN 。以下是提案Promise.any

它清楚地描述了它的作用

我不同意。对我来说,这个名字Promise.any让人困惑:

  • 如果任何一个承诺实现,它也会实现吗?是的。
  • 如果任何一个Promise 被拒绝,它也会拒绝吗?不会。
  • 如果任何承诺达成,它是否也会达成?这要视情况而定。
  • 它和 有什么不同Promise.race嗯。

我认为,每个方法的名称都应该明确定义该方法满足的条件。我建议采用以下命名约定:

Promise.all        -> Promise.allFulfilled
Promise.allSettled -> Promise.allSettled
Promise.race       -> Promise.oneSettled
Promise.any        -> Promise.oneFulfilled
Enter fullscreen mode Exit fullscreen mode

它反映了 Promise 状态的四种可能组合。它解释了为什么这些方法在提案中被称为组合器
。 当然,这种重命名是无法实现的Promise.all,因为Promise.race许多应用程序已经实现了并投入使用。但对于新方法来说,制定一些命名策略会非常有帮助。

我已经在 GitHub 上的提案存储库打开了问题Promise.any(),欢迎您分享您的想法。

接受拒绝

总的来说,我对新方法中引入的未抛出的“吞掉”拒绝的概念不太感冒。事实上,新的 Promise API 提供了一种静默忽略代码中错误的方法:

  • Promise.allSettled从不拒绝。
  • Promise.any仅当所有承诺都被拒绝时才拒绝。

目前没有其他核心 JavaScript API 可以做到这一点。忽略错误的唯一方法是手动将其包装成try..catch / .catch()空的主体。并在此处写上忽略错误的原因注释,否则eslint 会发出警告

我认为核心 API 应该暴露所有错误。是否处理错误始终由开发者决定。这应该对其他开发者清晰地展示。试想一下,如果错误地使用了吞噬拒绝,会耗费多少时间进行调试!尤其是在处理第三方代码时——某些代码无法正常工作,却没有抛出任何错误。

结论

我每天都会用到 Promise,就像许多其他开发者一样。我喜欢 JavaScript 的异步特性。清晰直观的 API 让我能够更快地完成任务,提高效率。因此,我认为 Promise API 应该被谨慎对待和修改。
感谢您的阅读,欢迎留言评论。

这篇文章最先出现在hackernoon.com上。

文章来源:https://dev.to/vitalets/what-s-wrong-with-promise-allsettled-and-promise-any-5e6o
PREV
Dev.to 作为无头 CMS
NEXT
6 个 JSON 工具助您提高工作效率