Возможность прерывания асинхронного вызова

Я использую babeljs с методами async/wait es7. У меня есть главный script, который вызовет метод async для массива объектов, которые возвращают promises. Я использую Promise.all(), чтобы дождаться, пока все возвратятся, однако эти задачи могут занять много времени, и если они превысят порог, я бы хотел отменить все из них и обработать задачу соответствующим образом.

Есть ли способ выполнить такую ​​вещь? В настоящее время единственный способ, о котором я могу думать, - это создать процесс, который выполняет работу по вызову этих методов и дожидаясь их решения, и если срок достигнет, он может убить процесс и выполнить любую необходимую обработку.

Обновление: некоторые пояснения к этим методам, которые ждут главный script... Они могут выполнять длинную серию операций (вызывать внешние системы, потоковые файлы где-то и т.д.) и не выполнять одно действие, которое могло бы отменяется независимо.

Обновление # 2: некоторый непроверенный код полу-psuedo

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

Ответ 1

Native ES6 promises в настоящее время не поддерживает отмену напрямую. Об этом постоянно говорят во многих местах, но он еще не существует.

Так как native promises не поддерживает его, а async/await работает на promises, в настоящее время нет встроенного простого способа его прервать. Один общий подход - использовать токен при создании действия, возвращающего обещание.

Скажем, вы многообещали 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();
    });
}

Теперь вы хотите использовать его:

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

Теперь, скажем, мы хотим отменить AJAX в некоторых случаях, мы можем передать токен как таковой:

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

Это позволит вам сделать:

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

Этот подход требует работы для работы с цепочкой, к счастью, с синтаксисом ES6 это не так много. Удачи и счастливого кодирования.

Ответ 2

Если вы можете перейти на Typescript (в каких типах это необязательно, а es6 и некоторые функции es7 поддерживаются из коробки) вместо Babel и использовать Bluebird promises, тип семантики отмены, которую вы ищете, может достигаются.

Я создал простой модуль, который заменяет помощника по умолчанию Typescript __awaiter на тот, который поддерживает отмену Bluebird: https://www.npmjs.com/package/cancelable-awaiter

С его помощью вы можете использовать синтаксис aync/wait в сочетании с promise.cancel() и promise.finally(), которые Bluebird дает вам.

Ответ 3

Это действительно зависит от API, который вам нужно использовать. Большинство существующих методов асинхронного API node нелегко "прерываться" (readfileasync и подобных), если только вы сами не выполняете их собственную реализацию.

Нет простого способа легко отменить запланированную отправку. API до сих пор не построен с этой целью. Promises также не может помочь, когда низкоуровневые реализации API не поддерживают прерывание.

Но в некоторых API вы можете перехватить "шаги" процесса, такие как потоки событий data и реализации "следующего тика". Там вы можете прервать дальнейшую обработку. (Потоки на самом деле довольно хороший кандидат для реализации перехвата IO)

Классический пример node, где вычисление последовательности фибоначчи ввода "n" подается на запрос, логика реализуется через "следующий тик". Там вы можете установить таймаут при вычислении, и сервер автоматически запускает длительные запросы:

var do_fibonacci_async = function(a,limiter,callback){
    if(limiter.halt){
        callback(limiter.msg);
    }
    else if(a <= 2){
        callback(limiter.halt ? limiter.msg : 1);
    }else{
        process.nextTick(function(){
            do_fibonacci_async(a - 1,limiter, function(val1){
                do_fibonacci_async(a - 2,limiter, function(val2){
                    callback(limiter.halt ? limiter.msg : val1+val2);
                });
            });
        });
    }
}

exports.fibonacci_async = function(a,callback){
            if(!a || isNaN(a)){
        callback(new out("fibonacci", [], ""));
        return;
    }

    var limiter = {halt:false, msg:"Too large to compute"};
    setTimeout(function(){
        limiter.halt = true;
    },5000);

    do_fibonacci_async(a,limiter,function(val){
        callback(new out("fibonacci", [a], val));
    });
}