web-dev-qa-db-ja.com

JavaScriptで、promiseをタイムアウトでラップする方法は?

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

OK、この話にひねりを加えましょう! myFunc自体がpromiseを返すと想像してください(注:promiseは延期されておらず、変更できません):

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

myFunctimeoutでラップすると、タイムアウトとは異なるエラーを処理する機能が失われます。 myFuncが進行状況イベントを発行する場合、これも失います。

したがって、問題は、timeout関数を変更して、エラー/進行状況情報を失うことなくpromiseを返す関数を受け入れることができるようにする方法です。

18
mnowotka
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();
}
9
jgillich

これは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));
    });
}

これは通常、私が復習を探しに行くときに私がやろうとしていることです(なぜ私はここにいるのですか)。ただし、問題は実行を遅らせることではなく、時間がかかりすぎるとエラーをスローすることでした。その場合、必要がなければタイムアウトを待ちたくないので、これは事態を複雑にします。そのため、2つの約束を「いつ」でラップすることはできません。ミックスで別の延期が必要なようです。 ( 複数のjQuery Deferredの最初のものが解決されるのを待ちますか? を参照してください。

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をmaster defer resolveに渡す必要はありません。これは、渡すことができるのは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;
    });
}
4
Somna

常に可能な限り低いレベルでpromsiifyする必要があります。基本から始めましょう。

ここではjQuerypromiseを使用しますが、これはBluebirdのようなより強力なライブラリを使用して実際に行う必要があります。delayを次のように作成して、簡単に始めましょう。

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

Delayは驚くべきことではないことに注意してください。遅延関数が行うのは、msミリ秒の遅延を引き起こすことだけです。

ここで、ライブラリ用に、promiseで機能するバージョンのdoSomethingを作成します。

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

Delay関数とdoSomethingAsync関数の両方に注意してください1つだけ実行してください。今、楽しみが始まります。

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.
});
3