为什么 Promise.any() 使用被拒绝承诺的 catch 处理程序的返回值来解决,而不是等待第一个履行的承诺?

Why does Promise.any() resolve with the returned value of the catch handler of a rejected promise instead of waiting for the first fulfilled one?

请考虑以下代码:

const race = () => Promise.any([
    // first promise, rejects in 100
    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => (console.log('one resolved with', r), r))
    .catch(err => (console.warn('one rejected with', err), err)),

    // second promise, resolves in 200
    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => (console.log('two resolved with', r), r))
    .catch(err => (console.warn('two rejected with', err), err))
  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

注意 为了简洁起见,我使用了逗号表达式。如果您更喜欢更传统的语法,这里是长格式:

const race = () => Promise.any([
    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => {
      console.log('one resolved with', r);
      return r;
    })
    .catch(err => {
      console.warn('one rejected with', err);
      return err;
    }),
    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => {
      console.log('two resolved with', r);
      return r;
    })
    .catch(err => {
      console.warn('two rejected with', err);
      return err;
    })
  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

我期待 race()200 中以 2 解决。相反,它在 100 中使用第一个 promise 的 catch 处理程序 (1) 的 return 值解析(即使它不是显式的——例如:如果我的 catch 会实际上 return 错误。.any() 包装器仍将使用 undefined 解决,而不是等待第一个已履行的承诺。解决,甚至不拒绝!)。

更有趣的是,如果我没有在失败的承诺中指定 catch 处理程序,预期的行为 确实会发生 。看看:

const race = () => Promise.any([

    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => (console.log('one resolved with', r), r)),

    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => (console.log('two resolved with', r), r))

  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

也许我想记录失败!为什么在拒绝时有一些代码 运行 会使父级 Promise.any() 解析为第一个失败承诺的 return 值,该承诺恰好有一个 .catch 处理程序, 而不是等待第一个实现的承诺, as advertised?

无论是否定义 catch 回调,父 Promise.any() 都不应该有相同的行为吗?

这是一个错误吗?我觉得这令人难以置信。


更新:在收到关于为什么会发生这种情况的解释后,我设法获得了我最初寻找的东西(在 catch 块中记录错误,同时仍在等待其他承诺中的第一个实现),通过 returning Promise.reject(err) 在捕获处理程序中:

const race = () => Promise.any([
    // first promise, rejects in 100
    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => (console.log('one resolved with', r), r))
    .catch(err => (console.warn('one rejected with', err), Promise.reject(err))),

    // second promise, resolves in 200
    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => (console.log('two resolved with', r), r))
    .catch(err => (console.warn('two rejected with', err), Promise.reject(err)))
  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

当您将 .catch 链接到一个 Promise 上时,整个表达式将计算为已解决的 Promise,该 Promise 的计算结果为 returned 在 .catch 末尾的值。看看下面如何不会导致第二个 .catch 被输入。

Promise.reject()
  .catch((err) => { console.log('first catch'); })
  .catch((err) => { console.log('second catch'); })

// In other words, the whole below expression resolves to a resolved Promise:
//   Promise.reject()
//     .catch((err) => { console.log('first catch'); })

整体来说

somePromise
  .catch(hander)

为了导致被拒绝的 Promise,handler 必须抛出(或构造和 return 一个被拒绝的 Promise)。

因此,在您的原始代码中:

.catch(err => (console.warn('one rejected with', err), err)),

这使整个表达得到解决。如果你想让它拒绝,把它改成:

.catch(err => {
  console.warn('one rejected with', err);
  throw err;
}),

(throw需要独立语句,不能和逗号一起使用)

const race = () => Promise.any([
    // first promise, rejects in 100
    new Promise((resolve, reject) => {
      setTimeout(reject, 100, 1);
    })
    .then(r => (console.log('one resolved with', r), r))
    .catch(err => {
      console.warn('one rejected with', err);
      throw err;
    }),

    // second promise, resolves in 200
    new Promise((resolve, reject) => {
      setTimeout(resolve, 200, 2);
    })
    .then(r => (console.log('two resolved with', r), r))
    .catch(err => (console.warn('two rejected with', err), err))
  ])
  .then(r => console.log('race resolved with', r))
  .catch(err => console.warn('race rejected with', err))

race()

解决类似问题的常见方法是 而不是 .catch 除非你可以对错误做一些有用的事情——如果不行,就让被拒绝的 Promise渗透回其调用者,以便其调用者可以处理它。虽然您可以 .catch 并重新抛出,但它有点难看,所以除非必要,否则您可能不想这样做。