skip to Main Content

I have an async operation that should be cancelable. So I decided to use Bluebird Promises to get it done.
However, when I try to run it, my code never completes, because (in case of the promise being canceled) everything gets stuck at "await".

Do you have any elegant solution for this problem?

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));

2

Answers


  1. You can invoke reject() in the onCancel callback and catch the exception by wrapping the await prm with try catch block or just let it throw:

    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));
    

    And Keith is right in the comment. You can use a 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));
    
    Login or Signup to reply.
  2. ES6 has this already built in AbortController, it’s maybe not obvious it can be used for other things, not just for fetch.

    Below is your example using the AbortController.

    Please note, when you abort, it’s your responsibility to either reject or resolve to keep code flowing. In most cases I usually reject, and if required catch this error as normal.

    ps. I also updated to make abort cancel the timeOut too, as for some reason you missed that bit out in your example.

    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));

    ps. It’s also possible to use 1 signal to cancel multiple promises, in this case it’s usually better to use addEventListener('abort', e) instead of sig.onabort = e

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search