【问题标题】:How to stop a Promise.map execution after an error with BluebirdBluebird 出错后如何停止 Promise.map 执行
【发布时间】:2021-09-29 12:02:17
【问题描述】:

我有大量数据要在数据库上创建,我想在并发限制的情况下并行生成它们。为此,我使用bluebird 中的Promise.map 方法并设置了concurrency 限制。但是,我还需要在其中一个承诺失败时立即停止执行链。

这是我当前的代码:

await Promise.map(
  entry.tags,
  async (tag) => {
    await factory(Tag)().create(tag);
  },
  { concurrency: 1000 },
).catch(() => {
  /** @todo find a better way */
  process.exit(1);
});

目前,如您所见,我正在使用process.exit(1),但我不会在那里处理退出代码。相反,Promise 链应该立即停止并且应该抛出错误以便从父函数中捕获。

p-map 库中的参数“stopOnError”完全符合我的预期,我在 Bluebird 上找不到类似的东西。 我也尝试在.catch() 中使用.cancel() 方法,但显然它不起作用。

【问题讨论】:

    标签: node.js promise bluebird


    【解决方案1】:

    您的承诺链正在立即停止并且向您的调用函数抛出错误。但是,这里发生了两件事:

    1. 您告诉 Bluebird 将最多 1000 个 Promise 并行排队,一旦创建了 Promise,就没有任何有意义的方法可以取消它。由于您的并发限制为 1000,您可能会看到 ~999 个 Promise 即使在一个失败后仍然有效。
    2. 通过在 Promise 上调用 .catch,您正在处理 await 否则会转换为您想要的错误的拒绝。

    请参阅 this fiddle,因为 Stack Snippets 不支持 Bluebird:

    (async function() {
        try {
        await Promise.map(
          seq(100),
          (i) => {
            console.log(i);
            if (Math.random() > 0.5)
              return Promise.resolve();
            else return Promise.reject();
          },
          { concurrency: 5 },
        ).catch(() => {
          console.error("catch 1");
        });
      } catch(e) {
        console.error("catch 2");
      }
    })();
    
    function seq(i) {
      return Array(i).keys();
    }
    

    在代码运行时,您会看到“catch 1”的日志,但不会看到“catch 2”的日志,因为 catch 块会导致外部承诺 resolve 而不是 rejecting。您还会注意到打印输出的数字少于 100 个;事实上,只有不到 5 个打印输出,因为 Bluebird 会尽快停止映射,并且在这个示例中,Promise 恰好失败得足够快,以至于 Bluebird 甚至没有机会填满所有并发工作人员。在您的实际代码中(可能是异步的),您可能会看到 1000 个工作人员中的大多数或全部在其中任何一个失败到足以取消之前启动。

    如果您想减少一次尝试次数,您可以减少并发参数,但同样无法保证在 Javascript 中取消 Promise。即使在 supports a Promise cancel method 的 Bluebird 中,“取消一个承诺仅仅意味着它的处理程序回调将不会被调用”。这些语义,与“中止”语义相比,文档标记为“不关心”语义,与 Promise.map 已经为您提供的语义是多余的。

    关于 catch 块:如果您要删除围绕我的“catch 1”的 catch 调用,您将在外部 try 块中的“catch 2”处失败 - 相当于在您喜欢的父函数。作为替代方案,您的 Promise 的 catch 块可能会重新抛出拒绝原因,这将导致“catch 1”和“catch 2”都打印在您的日志中:

    ).catch((t) => {
      console.error("catch 1");
      return Promise.reject(t);
    });
    

    【讨论】:

    • 这值得 OP 的回应。
    猜你喜欢
    • 2015-08-28
    • 1970-01-01
    • 2017-04-09
    • 1970-01-01
    • 2020-12-10
    • 1970-01-01
    • 2016-09-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多