Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ability to abort asynchronous call

I'm using babeljs with es7 style async/await methods. I have a main script that will call a async method on an array of objects that all return promises. I use Promise.all() to wait for all of those to return, however, these tasks could take a long time and if they exceed a threshold I would like to abort all of them, and the task handle that in an appropriate way.

Is there anyway to accomplish such a thing? Currently the only way that I can think of is by spawning a process that does the work of calling these methods and waiting for them to all resolve, and if the time limit is reach, it can kill the process and do whatever handling it needs.

Update: Some clarification about these methods that the main script is waiting on... They might be doing a long series of operations (calling external systems, streaming files somewhere, etc) and not performing one single action that could be canceled independently.

Update #2: Some untested semi-psuedo code

class Foo1 {
    async doSomething() {
        // call some external system
        // copy some files
        // put those files somewhere else (s3)
    }
}
class Foo2 {
    async doSomething() {
        // Do some long computations
        // Update some systems
    }
}

class FooHandler {
    constructor() {
        this.fooList = [];
    }

    async start() {
        await Promise.all(this.fooList.map(async (foo) => {
            return await foo.doSomething();
        }));
    }
}

let handler = new FooHandler();

handler.fooList.push(new Foo1());
handler.fooList.push(new Foo2());

// if this call takes too long because of slow connections, errors,   whatever,
// abort start(), handle it in whatever meaningful way, and continue on.
await handler.start();
like image 659
Greg A. Avatar asked Apr 12 '15 18:04

Greg A.


2 Answers

Native ES6 promises currently do not support cancellation directly. There are talks about it all the time in many places but it's not there yet.

Since native promises don't support it and async/await works on promises, there is currently no built in easy way to abort it. One common approach is to use a token when creating the action returning a promise.

Let's say you've promisified XHR GET:

// simplification
function ajax(url){
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest;
        xhr.open("GET", url);
        xhr.onload = () => resolve(xhr.responseText);
        xhr.onerror = reject;
        xhr.send();
    });
}

Now you want to use it:

async function foo(){
    let result = await ajax("/myApi");
    let result2 = await ajax("/myApi2?token=" + result);
}

Now, let's say we want to cancel the AJAX in some cases, we can pass a token as such:

function ajax(url, token = {}){
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest;
        xhr.open("GET", url);
        Object(token).cancel = () => { xhr.abort(), reject(); };
        xhr.onload = () => resolve(xhr.responseText);
        xhr.onerror = reject;
        xhr.send();
    });
}

This would let you do:

async function foo(){
    let token = {};
    let req = ajax("/myApi", token); // note no await
    // now let's say we want to abort the request since we don't 
    // need the data
    token.cancel(); // this will abort the token
}

This approach needs work to work with chaining, luckily with ES6 syntax this is not as big of a deal. Good luck and happy coding.

like image 59
Benjamin Gruenbaum Avatar answered Oct 03 '22 07:10

Benjamin Gruenbaum


If you can migrate to Typescript (in which types are optional and es6 and some es7 features are supported out of the box) instead of Babel and use Bluebird promises, the kind of cancellation semantics you are looking for can be achieved.

I've created a simple module which replaces the default Typescript __awaiter helper with one that supports Bluebird cancellations: https://www.npmjs.com/package/cancelable-awaiter

With it you can use aync/await syntax in conjunction with promise.cancel() and promise.finally() which Bluebird gives you.

like image 24
Itay Avatar answered Oct 03 '22 08:10

Itay