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

mno*_*tka 18 javascript jquery promise deferred jquery-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();
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以执行一些调用的异步函数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' );
    }
);
Run Code Online (Sandbox Code Playgroud)

好吧,让我们来讲一下这个故事吧!想象一下,它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();
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我换myFunctimeout,我会宽松处理不同的错误,然后超时的能力.如果myFunc发出进度事件,我也会松开它.

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

jgi*_*ich 9

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();
}
Run Code Online (Sandbox Code Playgroud)


Som*_*mna 5

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

我认为 Benjamin 很接近,你会希望你的超时被单独处理,所以我们将从他的延迟功能开始。

function delay(ms){
    var d = $.Deferred();
    setTimeout(function(){ d.resolve(); }, ms);
    return d.promise();
}
Run Code Online (Sandbox Code Playgroud)

然后,如果您想在代码执行之前等待,您可以调用您希望由于此承诺而延迟的方法。

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));
    });
}
Run Code Online (Sandbox Code Playgroud)

这通常是我去寻找复习时想要做的(为什么我在这里)。然而,问题不是关于延迟执行,而是如果时间太长会抛出错误。在这种情况下,这会使事情变得复杂,因为如果您不需要,您不想等待超时,因此您不能将两个承诺包装在“何时”中。看起来我们需要另一个延迟混合。(请参阅等待多个 jQuery Deferreds 中的第一个被解决?

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;
    });
}
Run Code Online (Sandbox Code Playgroud)

我们可以简化它,知道在这种情况下,主延迟仅在超时首先发生时拒绝,并且仅在 functPromise 首先解决时才解决。因此,我们不需要将 functPromise 传递给主 defer 解析,因为它是唯一可以传递的东西,而且我们仍在作用域内。

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;
    });
}
Run Code Online (Sandbox Code Playgroud)