I did not find a straightforward way to stop execution of a Promise when using AbortController. Let me explain.
To await async function _aget_Cards within a time limit defined by timeout parameter, I created two Promises and raced them as per the code block below.
async function xxx (userCN :string, timeout :number) :Promise<CardDetailsWithCCBal[]|unknown> {
const controller = new AbortController()
const raced_promise = Promise.race ([
new Promise ((resolve, reject) => {
_aget_Cards (resolve, reject, userCN, controller)
}),
new Promise ( (resolve, reject) => {
setTimeout ( () => reject('CONTROLLER_TIMEOUT'), timeout)
})
])
raced_promise.catch((error) => {
console.log ('Error on raced promise detected')
if (error === 'CONTROLLER_TIMEOUT') {
console.log ('Detected error is a CONTROLLER_TIMEOUT ... Sending "abort" signal to controller for _aget_Cards')
controller.abort()
}
})
return raced_promise
}
You will note that I raised a specific 'CONTROLLER_TIMEOUT' error with reject('CONTROLLER_TIMEOUT') if the time limit Promise completes earlier. In the handling of that specific error, I invoke an abort() on the AbortController with controller.abort().
The callbacks resolve, reject and the AbortController instance controller are passed as parameters in the first Promise _aget_Cards (resolve, reject, userCN, controller).
In the controller.signal.addEventListener of the _aget_Cards function, I set doAbort = true, as per code block below.
async function _aget_Cards (resolve :(value :unknown) => void, reject :(reason ?:any) => void, userCN :string, controller :AbortController) {
let doAbort = false
controller.signal.addEventListener ("abort", () => {
console.log ('Controller received "abort" signal ... aborting ...')
doAbort = true
})
let start_time = Date.now()
if (doAbort) return
const cardDetails_lists :CardDetails[][] = await Promise.all ( [
aget_CardDetails_List (userCN, true), // Get Primary cards
aget_CardDetails_List (userCN, false) // Get Secondary cards
])
console.log(`List of Primary and Secondary Cards obtained in ${(Date.now()-start_time)/1000}s`)
start_time = Date.now()
if (doAbort) return
const cardDetails_list :CardDetails[] = [...cardDetails_lists[0], ...cardDetails_lists[1]]
cardDetails_list.sort ( (a, b) => {
let a_sortVal :string = `${a.cardType}${a.cardNo}`
let b_sortVal :string = `${b.cardType}${b.cardNo}`
return (a_sortVal < b_sortVal) ? -1 : (a_sortVal > b_sortVal) ? 1 : 0
})
if (doAbort) return
...
You will note that I have been using if (doAbort) return in more than one place to abort execution. I did not find a straightforward way to force the return when an abort was detected.
Rejecting the promise with reject inside controller.signal.addEventListener does not affect the execution of _aget_Cards. Neither does raising an error.
Your help would be much appreciated.
Yes, it's not as straighforward as registering the abort signal with
async functionexecution to abort execution (typically duringawait) when the signal is raised. JavaScript has no such feature :-/Testing manually means that you stay in control of the places where control flow may be aborted. In your case, you are e.g. testing before and after the sorting, so that the sorting itself cannot be "interrupted" - it executes either all or nothing.
However, you can simplify your code quite a bit:
Promise.racewith a promise that rejects after a certain time. Just abort thecontrollerafter a certain timeAbortControllermanually and doingsetTimeoutandcontroller.abort()yourself, use theAbortSignal.timeout()helper functionasync functionas the executor tonew Promise! You already get the promise by just calling the function, and you won't have to deal with callingresolve()/reject()at the right timeAbortSignalto the function that should be stopped, not the wholeAbortController. You pass the controller to functions that should be able to stop others.doAbortthat you set yourself in an event listener on the abort signal. Just refer tosignal.abortedif (signal.aborted) return, rather callsignal.throwIfAborted()that will throw the abort reason and have it propagate up the call stack as an exceptionSo your code becomes
Finally, you don't need to call
signal.throwIfAborted()that often. The signal would not fire while your synchronous code executes, so if it was not already aborted before the sorting it won't be aborted immediately after the sorting either. You normally only need this method to check after anawaitwhether the signal has been triggered in the meantime. Even better though would be make the promise that you'reawaiting reject early when the signal fires, by passing the signal to the function you're calling and handling it inside there: