Proper way to Abort (stop) running async/await function?
Asked Answered
B

1

8

There has been other topics on SE, but most of them are dated 5 years ago. What is the current, up-to-date approach to cancel await call in JS? i.e.

async myFunc(){
    let response = await oneHourLastingFunction();
    myProcessData(response);
}

at specific moment application decides it no longer want to wait that oneHourLastingFunction, but it is stuck in "await". How to cancel that? Any standard ways of cancelation-tokens/abortControllers for promises?

Boatload answered 23/11, 2021 at 12:30 Comment(7)
"What is the current, up-to-date approach to cancel await call in JS?" other than being impossible? There hasn't been any new developments there. Unless you specifically make your own function cancellable, that is.Delete
An answer saying "the old questions you found are still up to date"? Sounds like a duplicate to me.Delete
@Delete and what about AbortControllers? javascript.plainenglish.io/…Boatload
@vlaz if there is no update in last 6 years, what a pity. you can mark topic as duplicate.Boatload
You can use Promise.race([oneHourLastingFunction(), delay(time)]), However it doesn't cancel other operation... So you should use AbortControllers, Most API support this...Financier
The general mechanism is you throw an exception. AbortController is a standardized mechanism for this. It's opt-in. You can't arbitrarily cancel any async operation.Proctoscope
Geez this thread is a sad indictment of the state of JavaScript. How can web interfaces not be built to be interruptable as a first class language construct?Libriform
I
8

Canceling an asynchronous procedure is still not a trivial task, especially when you need deep cancellation and flow control. There is no native solution at the moment. All you can do natively:

  • pass AbortController instance to each nested async function you want to make cancellable
  • subscribe all internal micro-tasks (requests, timers, etc) to the signal
  • optionally unsubscribe completed micro-tasks from the signal
  • call abort method of the controller to cancel all subscribed micro-tasks

This is quite verbose and a tricky solution with potential memory leaks.

I can just suggest my own solution to this challenge- c-promise2, which provides cancelable promises and a cancelable alternative for ECMA asynchronous functions - generators.

Here is an basic example (Live Demo):

import { CPromise } from "c-promise2";

// deeply cancelable generator-based asynchronous function
const oneHourLastingFunction = CPromise.promisify(function* () {
  // optionally just for logging
  this.onCancel(() =>
    console.log("oneHourLastingFunction::Cancel signal received")
  );
  yield CPromise.delay(5000); // this task will be cancelled on external timeout
  return "myData";
});

async function nativeAsyncFn() {
  await CPromise.delay(5000);
}

async function myFunc() {
  let response;
  try {
    response = await oneHourLastingFunction().timeout(2000);
  } catch (err) {
    if (!CPromise.isCanceledError(err)) throw err;
    console.warn("oneHourLastingFunction::timeout", err.code); // 'E_REASON_TIMEOUT'
  }
  await nativeAsyncFn(response);
}

const nativePromise = myFunc();

Deeply cancellable solution (all functions are cancellable) (Live Demo):

import { CPromise } from "c-promise2";

// deeply cancelable generator-based asynchronous function
const oneHourLastingFunction = CPromise.promisify(function* () {
  yield CPromise.delay(5000);
  return "myData";
});

const otherAsyncFn = CPromise.promisify(function* () {
  yield CPromise.delay(5000);
});

const myFunc = CPromise.promisify(function* () {
  let response;
  try {
    response = yield oneHourLastingFunction().timeout(2000);
  } catch (err) {
    if (err.code !== "E_REASON_TIMEOUT") throw err;
    console.log("oneHourLastingFunction::timeout");
  }
  yield otherAsyncFn(response);
});

const cancellablePromise = myFunc().then(
  (result) => console.log(`Done: ${result}`),
  (err) => console.warn(`Failed: ${err}`)
);

setTimeout(() => {
  console.log("send external cancel signal");
  cancellablePromise.cancel();
}, 4000);
Incur answered 24/11, 2021 at 0:52 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.