Search code examples
javascriptnode.jspromisees6-promisecancellation

Javascript Canceling Function that contains list of await tasks


I was wondering if there is any to cancel / stop execution of a javascript function that contains multiple await functions. Due to the nature of promises and their lack of proper cancellations, is there any other implementation or library to help me achieve something like this?

async function run(x,y,z) {
   return new Promise(async(resolve,reject) => {  
        await doSomething(x)
        await doSomething(y)
        //cancel could be happen around here and stop the last "doSomething"
        await doSomething(z)
   })
}

setTimeout(() => {
     run.cancel()
},500) //cancel function after 500ms



Solution

  • To just stop the advancement from one function call to the next, you can do something like this:

    function run(x, y, z) {
        let stop = false;
        async function run_internal() {
            await doSomething(x)
            if (stop) throw new Error("cancelled");
            await doSomething(y)
            if (stop) throw new Error("cancelled");
            await doSomething(z)
        }
        return {
            cancel: () => {
                stop = true;
            },
            promise: run_internal();
        };
    }
    
    const retVal = run(a, b, c);
    retVal.promise.then(result => {
        console.log(result);
    }).catch(err => {
        console.log(err);
    })
    
    setTimeout(() => {
        retVal.cancel()
    }, 500); //cancel function after 500ms
    

    Javascript does not have a generic way to "abort" any further execution of a function. You can set a flag via an external function and then check that flag in various points of your function and adjust what you execute based on that flag.

    Keep in mind that (except when using workerThreads or webWorkers), Javascript runs your code in a single thread so when it's running, it's running and none of your other code is running. Only when it returns control back to the event loop (either by returning or by hitting an await) does any of your other code get a chance to run and do anything. So, "when it's actually running", your other code won't be running. When it's sitting at an await, your other code can run and can set a flag that can be checked later (as my example above shows).

    fetch() in a browser has some experimental support for the AbortController interface. But, please understand that once the request has been sent, it's been sent and the server will receive it. You likely won't be aborting anything the server is doing. If the response still hasn't come back yet or is in the process of coming back, your abort may be able to interrupt that. Since you can't really know what is getting aborted, I figure it's better to just put a check in your own code so that you won't process the response or advance to further processing based on a flag you set.

    You could wrap this flag checking into an AbortController interface if you want, but it doesn't change the fundamental problem in any way - it just affects the API you expose for calling an abort.