【问题标题】:If any Promise is rejected, how do you count the number of success in Promise.all?如果有任何 Promise 被拒绝,如何计算 Promise.all 中的成功次数?
【发布时间】:2021-08-29 20:11:08
【问题描述】:

我目前正在尝试确定是否可以执行以下操作:

async function(x) {
  ...
}

try {
  await Promise.all([function(1), function(2), function(3), ...]);
} catch (err) {
  // Count number of successful Promises resolved at the point when one is rejected
  return statistics(num_success);
}

这可能吗?我尝试在 catch 块中执行此操作的原因是,如果出现任何错误,我可以立即终止该函数。

如果可以的话,我怎样才能做到这一点?

【问题讨论】:

  • 查看 Promise.allSettled
  • @RaphaelPICCOLO 但不会 Promise.allSettled 意味着发生任何错误时程序不会立即终止?
  • @zoobiedoobie 但您希望它立即终止,对吧?如果您想在第一个错误时终止,那么检查成功履行了多少承诺有什么意义?
  • 此外,一旦其中一个承诺失败,似乎对Promise.all 的“终止”存在误解:仅仅因为Promise.all 拒绝了它的参数的第一个被拒绝的承诺,那 并不意味着待处理的 Promise 停止执行。除非您明确终止该进程,否则运行时将继续,直到事件队列中没有任何内容(即所有承诺都已解决或被拒绝)

标签: node.js promise


【解决方案1】:

如果您希望您的所有承诺在它们得到解决(履行或拒绝)之前一直运行,您可以使用Promise.allSettled 执行此操作,如Raphael PICCOLO 的评论和SrHenry's answer

但是,为了保持 Promise.all 立即响应拒绝的行为,您需要一些额外的自定义行为。这听起来是包装 Promse.all 的一个很好的理由。

/**
 * Receives an array, like Promise.all, and runs until the first rejection.
 *
 * Unlike Promise.all, but like Promise.allSettled, this will always resolve
 * to an object; however, like Promise.all, this will resolve as soon as it
 * encounters the first rejection.
 *
 * Returns a promise that resolves to an object with these properties:
 *   success: boolean, whether Promise.all would have succeeded
 *   count: number, number of resolved Promises at the time of return
 *   results: Array, result array containing all resolved Promises/values
 *   error: any, the reject value that caused this to fail, or null
 */
function allWithProgress(arrayOfPromises) {
  const results = new Array(arrayOfPromises);
  let count = 0;
  
  /**
   * Given an input to Promise.resolve, increments results+count 
   * when complete.
   */
  function wrap(valueOrThenable, index) {
    return Promise.resolve(valueOrThenable).then(x => {
      results[index] = x;
      count++;
      return x;
    });
  }
  
  // slice(0) prevents the results array from being modified.
  // You could also add a condition check that prevents `wrap` from
  // modifying the results after it returns.
  return Promise
    .all(arrayOfPromises.map(wrap)) // or "(e, i) => wrap(e, i)"
    .then(x => ({success: true, count, results: results.slice(0), error: null}))
    .catch(e => ({success: false, count, results: results.slice(0), error: e}));
}

// Test harness below

function timeoutPromise(string, timeoutMs) {
  console.log("Promise created: " + string + " - " + timeoutMs + "ms");
  return new Promise(function(resolve, reject) {
    window.setTimeout(function() {
      console.log("Promise resolved: " + string + " - " + timeoutMs + "ms");
      resolve(string);
    }, timeoutMs);
  });
}

Promise.resolve().then(() => {
  // success
  return allWithProgress([
    timeoutPromise("s1", 1000),
    timeoutPromise("s2", 2000),
    timeoutPromise("s3", 3000),
    "not a promise"
  ]).then(console.log);
}).then(() => {
  // failure
  return allWithProgress([
    timeoutPromise("f1", 1000),
    timeoutPromise("f2", 2000)
      // rejects with a String for Stack Snippets; use an Error in real code 
      .then(() => Promise.reject("f2 failed")),
    timeoutPromise("f3", 3000),
    "not a promise"
  ]).then(console.log);
});

请注意,ES6 Promises 不能以任何标准方式取消或回滚,因此在测试用例中 f3 不会阻止完成。如果在飞行中停止这些承诺很重要,那么您需要自己编写该逻辑。

【讨论】:

  • 虽然这段代码确实解决了最初的要求,即在第一个 Promise 拒绝时只计算已经实现的 Promise,但请注意,这不会阻止运行时执行仍然未决的承诺,直到它们被拒绝或解决。这也可以在上面的 sn-p 的输出中看到,即它仍然会打印Promise resolved f3,即使它只在 f2 已经被拒绝后才解析(虽然它不会被计入输出)!
  • 如果这种行为是有意的,这只能通过让所有的 Promise 都知道一些共享状态来实现,并且一旦至少有一个 Promise 被拒绝,每个 Promise 都必须拒绝。
  • 请注意,我无意诋毁这个精心制定的答案。我只是想指出该技术的局限性(以及 OP 方面可能存在的误解)
  • @derpirscher 这是一个公平的补充;谢谢你。我很清楚该属性,并故意编写了测试用例来说明它,但是您是对的,我没有在文档中指出它。也就是说,无法有意义地中止或回滚 Promise 是 ES6 Promise 的固有属性,而不是提问者暗示或挑战的属性,所以我将把它作为脚注,除非 OP 澄清他们需要行为。
  • 我当然认为 非常清楚这些限制,所以我的 cmets 是针对 OP ... 因为原始问题和下面的 cmets 的制定方式,我的印象是,OP 希望剩余的承诺立即停止执行。
【解决方案2】:

正如Raphael PICCOLO 所推荐的,您可以使用Promise.allSettled<T = any>(...promises: Promise<T>[])。它返回一个包含具有status 属性的对象的数组的promise,根据每个promise 的履行或拒绝,分配有“fulfilled”或“rejected”。如果 promise 已履行,value 属性将可用,如果它拒绝,reason 属性将可用。

示例代码:

//...

const promises = []; //Array with promises.
let count = 0;

Promise.allSettled(promises)
    .then(settled => {
        settled.forEach(({ status, value, reason }) => {
            if (status === 'fulfilled')
                count++;
        })
    })
    .then(() => statistics(count));

//...

【讨论】:

  • 虽然您在原则上对Promise.allSettled 是正确的,但您的代码没有任何意义,因为Promise.allSettled 使用数组解析,您必须对其进行迭代。您的第一个 then 只需要一个对象。
  • 哦,天哪,这是真的,@derpirscher,让我纠正这个
猜你喜欢
  • 2014-11-07
  • 1970-01-01
  • 1970-01-01
  • 2018-01-27
  • 2020-08-01
  • 1970-01-01
  • 2018-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多