在JavaScript中,如何在超时中包装一个promise?

时间:2014-06-12 11:42:58

标签: javascript jquery promise jquery-deferred deferred

使用deffered / promise实现某些异步函数超时的常见模式:

// Create a Deferred and return its Promise
function timeout(funct, args, time) {
    var dfd = new jQuery.Deferred();

    // execute asynchronous code
    funct.apply(null, args);

    // When the asynchronous code is completed, resolve the Deferred:
    dfd.resolve('success');

    setTimeout(function() {
        dfd.reject('sorry');
    }, time);
    return dfd.promise();
}

现在我们可以执行一些名为myFunc的异步函数并处理超时:

// Attach a done and fail handler for the asyncEvent
$.when( timeout(myFunc, [some_args], 1000) ).then(
    function(status) {
        alert( status + ', things are going well' );
    },
    function(status) {
        alert( status + ', you fail this time' );
    }
);

好的,让我们在这个故事中扭转局面!想象一下,myFunc本身会返回一个承诺(注意:承诺不会延期而且我无法改变它):

function myFunc(){
    var dfd = new jQuery.Deffered();
    superImportantLibrary.doSomething(function(data)){
       if(data.length < 5){
            dfd.reject('too few data');
       }
       else{
           dfd.resolve('success!');
       }
    }, {'error_callback': function(){
        dfd.reject("there was something wrong but it wasn't timeout");}
    }});
    return dfd.promise();
}

现在如果我将myFunc包裹在timeout中,我将失去处理不同于超时的错误的能力。如果myFunc发出进度事件,我也会放弃它。

所以问题是:如何修改timeout函数,以便它可以接受返回promises的函数而不会丢失它们的错误/进度信息?

3 个答案:

答案 0 :(得分:8)

function timeout(funct, args, time) {
    var deferred = new jQuery.Deferred(),
        promise = funct.apply(null, args);

    if (promise) {
        $.when(promise)
            .done(deferred.resolve)
            .fail(deferred.reject)
            .progress(deferred.notify);
    }

    setTimeout(function() {
        deferred.reject();
    }, time);

    return deferred.promise();
}

答案 1 :(得分:3)

你应该始终在尽可能最低的水平上进行宣传。让我们从基础开始。

我将在这里使用jQuery promises,但这应该通过更强大的库来完成,比如Bluebird让我们开始简单,通过创建我们的delay

function delay(ms){
    var d = $.Deferred();
    setTimeout(function(){ d.resolve(); }, ms);
    return d.promise();
}

注意延迟没有任何意外,我们所有的延迟函数都会导致延迟ms毫秒。

现在,对于您的图书馆,我们希望创建一个适用于承诺的doSomething版本:

 superImportantLibrary.doSomethingAsync = function(){
     var d = $.Deferred();
     superImportantLibrary.doSomething(function(data){ d.resolve(data); });
     return d.promise();
 };

请注意我们的延迟和doSomethingAsync功能只做一件事。现在好玩的开始了。

function timeout(promise,ms){
    var timeout = delay(ms); // your timeout
    var d = $.Deferred();
    timeout.then(function(){ d.reject(new Error("Timed Out")); });
    promise.then(function(data){ d.resolve(data); });
    return d.promise();
}

timeout(superImportantLibrary.doSomethingAsync(),1000).then(function(data){
     // handle success of call
}, function(err){
     // handle timeout or API failure.
});

现在在Bluebird中,整个代码应该是:

superImportantLibrary.doSomethingAsync().timeout(1000).then(function(){
    // complete and did not time out.
});

答案 2 :(得分:3)

我意识到这是2岁,但万一有人正在寻找答案......

我认为本杰明很接近你会希望你的超时分开处理,所以我们将从他的延迟功能开始。

function delay(ms){
    var d = $.Deferred();
    setTimeout(function(){ d.resolve(); }, ms);
    return d.promise();
}

然后,如果你想在代码执行之前等待,你可以调用你想要延迟的方法作为这个承诺。

function timeout(funct, args, time) {
    return delay(time).then(function(){
        // Execute asynchronous code and return its promise
        // instead of the delay promise. Using "when" should
        // ensure it will work for synchronous functions as well.
        return $.when(funct.apply(null, args));
    });
}

这通常是我在寻找复习时想要做的事情(为什么我在这里)。然而,问题不在于延迟执行,而是在花费太长时间时抛出错误。在这种情况下,这会使事情变得复杂,因为如果你不需要,你不想等待超时,所以你不能将两个promises包装在“when”中。看起来我们需要在混合中推迟另一个。 (见Wait for the first of multiple jQuery Deferreds to be resolved?

function timeout(funct, args, time) {
    var d = $.Deferred();

    // Call the potentially async funct and hold onto its promise.
    var functPromise = $.when(funct.apply(null, args));

    // pass the result of the funct to the master defer
    functPromise.always(function(){
        d.resolve(functPromise)
    });

    // reject the master defer if the timeout completes before
    // the functPromise resolves it one way or another
    delay(time).then(function(){
        d.reject('timeout');
    });

    // To make sure the functPromise gets used if it finishes
    // first, use "then" to return the original functPromise.
    return d.then(function(result){
        return result;
    });
}

我们可以简化这一点,因为在这种情况下,主延迟只会在超时发生时拒绝,并且只有在functPromise首先解析时才会解决。因此,我们不需要将functPromise传递给主延迟解析,因为它是唯一可以传递的东西,我们仍然在范围内。

function timeout(funct, args, time) {
    var d = $.Deferred();

    // Call the potentially async funct and hold onto its promise.
    var functPromise = $.when(funct.apply(null, args))
        .always(d.resolve);

    // reject the master defer if the timeout completes before
    // the functPromise resolves it one way or another
    delay(time).then(function(){
        d.reject('timeout');
    });

    // To make sure the functPromise gets used if it finishes
    // first, use "then" to return the original functPromise.
    return d.then(function(){
        return functPromise;
    });
}