如何将异步 await 与 Bluebird 可取消承诺相结合

How to combine async await with Bluebird cancelable promises

提问人:stoefln 提问时间:5/31/2023 更新时间:5/31/2023 访问量:143

问:

我有一个应该可以取消的异步操作。所以我决定使用Bluebird Promises来完成它。 但是,当我尝试运行它时,我的代码永远不会完成,因为(如果承诺被取消)一切都停留在“等待”状态。

对于这个问题,你有什么优雅的解决方案吗?

Codesandbox:https://codesandbox.io/p/sandbox/bluebird-playground-forked-dfbp61?welcome=true

const Promise = require("bluebird");

Promise.config({ cancellation: true });

let myP = () =>
  new Promise((resolve, reject, onCancel) => {
    // In this example, we use setTimeout(...) to simulate async code that runs 2 seconds


    onCancel(() => {
      console.log("onCancel called");
    });
    setTimeout(function () {
      console.log("before resolve");
      resolve("Success!"); // Yay! Everything went well!
    }, 2500);
  });

const run = async () => {
  console.log("Start");

  const prm = myP();
  setTimeout(() => {
    prm.cancel();
  }, 1000);

  await prm;
  console.log("After await prm"); // <- this code is never executed, what can I do?
};

run()
  .then(() => console.log("Finished")) // <- "then" is never called, what can I do?
  .catch((e) => console.error(e));
javascript async-await promise 蓝鸟

评论

1赞 Keith 5/31/2023
使用 bluebird 使 promise 可取消可能有点矫枉过正,并且有一种使用 AbortController 的标准方法可以做到这一点。
0赞 stoefln 5/31/2023
@Keith 使用 Bluebird 有什么矫枉过正?在性能或代码复杂性或其他方面?我觉得这很简单......
0赞 Keith 5/31/2023
使用 Bluebird 没有错,这是一个很好的库,但仅仅为了获得取消功能而安装它就太过分了,尤其是当它已经内置在浏览器中时。

答:

0赞 Matt Peng 5/31/2023 #1

您可以在 onCancel 回调中调用并通过包装 with try catch 块来捕获异常,或者让它抛出:reject()await prm

const Promise = require("bluebird");

Promise.config({ cancellation: true });

let myP = () =>
  new Promise((resolve, reject, onCancel) => {
    onCancel(() => {
      console.log("onCancel called");
      reject();
    });
    setTimeout(() => resolve("Success!"), 2500);
  });

const run = async () => {
  console.log("Start");
  const prm = myP();
  setTimeout(() => prm.cancel(), 1000);
  await prm;
};

run()
  .then(() => console.log("Finished"))
  .catch((e) => console.error(e));

基思在评论中是对的。您可以使用 .AbortController

const controller = new AbortController();

let myP = (sig) =>
  new Promise((resolve, reject) => {
    if (sig?.aborted) return reject()
    let timer
    timer = setTimeout(() => resolve("Success!"), 2500);
    function handleAbort() {
      clearTimeout(timer)
      signal?.removeEventListener("abort", handleAbort)
      reject()
    }
    signal?.addEventListener("abort", handleAbort);
  });

const run = async () => {
  console.log("Start");
  const prm = myP(controller.signal);
  setTimeout(() => controller.abort(), 1000);
  await prm;
};

run()
  .then(() => console.log("Finished"))
  .catch((e) => console.error(e));

评论

0赞 stoefln 5/31/2023
你的例子不起作用。“After await prm”永远不会被记录。
0赞 Matt Peng 5/31/2023
@stoefln会抛出错误?如果是这样,请用 try catch 块包装它。await prm
0赞 stoefln 5/31/2023
自己试试吧。这是行不通的。
2赞 Keith 5/31/2023 #2

ES6 已经内置了这一点,它可能不明显可以用于其他事情,而不仅仅是用于获取。AbortController

下面是使用 .AbortController

请注意,当您中止时,您有责任或保持代码流动。在大多数情况下,我通常会拒绝,如果需要,我会像往常一样发现这个错误。rejectresolve

我还更新了使中止取消超时,因为出于某种原因,您在示例中错过了这一点。

let myP = (sig) =>
  new Promise((resolve, reject) => {
    const tm = setTimeout(function () {
      console.log("before resolve");
      resolve("Success!"); // Yay! Everything went well!
    }, 2500);
    //sig.onabort = () => {
    sig.addEventListener('abort', () => {    
      console.log('aborted');
      clearTimeout(tm);
      resolve();
    });
  });

const run = async () => {
  console.log("Start");

  const sig = new AbortController();
  const prm = myP(sig.signal);  
  setTimeout(() => sig.abort(), 1000);

  await prm;
  console.log("After await prm"); 
};

run()
  .then(() => console.log("Finished")) 
  .catch((e) => console.error(e));

也可以使用 1 个信号来取消多个 promise,在这种情况下,通常最好使用 而不是addEventListener('abort', e)sig.onabort = e