能够中止异步调用

时间:2015-04-12 18:01:47

标签: javascript node.js asynchronous babeljs ecmascript-7

我使用带有es7样式的babeljs async / await方法。我有一个主脚本,它将在所有返回promises的对象数组上调用异步方法。我使用Promise.all()来等待所有这些返回,但是,这些任务可能需要很长时间,如果超过阈值,我想中止所有这些任务,并且任务以适当的方式处理。

反正有没有完成这样的事情?目前,我能想到的唯一方法是生成一个进程,该进程执行调用这些方法的工作并等待它们全部解决,如果达到时间限制,它可以终止进程并执行它需要的任何处理。

更新:关于主脚本正在等待的这些方法的一些说明......他们可能正在进行一系列的操作(调用外部系统,在某处调用流文件等)而不执行可能被取消的单个操作独立。

更新#2:一些未经测试的半假代码

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

3 个答案:

答案 0 :(得分:9)

原生ES6承诺目前不支持直接取消。在很多地方一直都在谈论它,但它还没有。

由于本机承诺不支持它并且async / await适用于promises,因此目前没有内置的简单方法可以中止它。一种常见的方法是在创建返回promise的操作时使用令牌。

让我们说你已经宣传了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's say we want to abort the request since we don't 
    // need the data
    token.cancel(); // this will abort the token
}

这种方法需要工作才能使用链接,幸运的是,使用ES6语法这并不是一件大事。祝你好运,编码愉快。

答案 1 :(得分:1)

如果你可以迁移到Typescript(其中类型是可选的,es6和一些es7特性支持开箱即用)而不是Babel并使用Bluebird promises,那么你可以实现你正在寻找的那种取消语义。 / p>

我创建了一个简单的模块,用一个支持Bluebird取消的帮助器取代了默认的Typescript __awaiter助手:https://www.npmjs.com/package/cancelable-awaiter

有了它,您可以将aync / await语法与Bluebird为您提供的promise.cancel()promise.finally()结合使用。

答案 2 :(得分:0)

这实际上取决于您需要使用的API。节点的大多数当前异步API方法都不容易“可中断”(readfileasync等),除非您自己实现它们。

没有简单的方法可以轻松取消预定的调度。到目前为止,API并没有考虑到这一点。当API的低级实现不支持中止时,Promise也无能为力。

但在某些API中,您可以拦截流程“步骤”,例如data事件上的流和“下一个滴答”实现。在那里你可以中止进一步的处理。 (Streams实际上非常适合实现可拦截IO的东西)

经典节点示例,其中每个请求提供输入“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));
    });
}