2014-06-12 7 views
16

Es ist ein gemeinsames Muster Timeout von einigen asynchronen Funktion zu implementieren, DEFFERED/Versprechen mit:in JavaScript, wie ein Versprechen in Timeout zu wickeln?

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

Jetzt haben wir einige asynchrone Funktion myFunc und Griff Timeout genannt ausführen kann:

// 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, lassen Sie uns machen eine Wendung in dieser Geschichte! Stellen Sie sich vor, dass die myFunc selbst gibt ein Versprechen (ANMERKUNG: versprechen, nicht verschoben, und ich kann es nicht ändern):

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

Nun, wenn ich myFunc in timeout wickeln, werde ich die Fähigkeit verlieren, um Fehler zu behandeln unterschiedliche dann Timeout. Wenn myFunc Fortschritt Ereignisse emittieren, werde ich dies auch verlieren.

Also die Frage ist: Wie zu ändern timeout Funktion, so dass es Funktionen akzeptieren Versprechen akzeptieren kann, ohne ihre Fehler/Fortschritt Informationen zu verlieren?

+0

Ihre Primitiven falsch sind, müssen Sie es in zwei Stufen promisify zunächst - promisify die superImportantLibrary.doSomething Methode und erst dann das Versprechen Rückkehr auszuführen. Vermeiden Sie auch jQuery Versprechungen, sie sind schrecklich im Vergleich zu anderen Implementierungen. –

+0

@BenjaminGruenbaum - Welche Implementierungen? Warum jQuery verspricht sind schrecklich? Was meinst du mit "deine Primitiven sind falsch"? Wie kann ich "superImportantLibrary.doSomething" promisifizieren, wenn es eine Bibliothek ist und nicht mein eigener Code? Kannst du einen Beispielcode schreiben, um zu erklären, was du damit meinst? – mnowotka

+1

Ich hatte Angst, ich wäre nicht in der Lage, solche Behauptungen zu machen, ohne mich rechtfertigen zu müssen :) Also [wie man eine API in Versprechen umwandelt] (http://stackoverflow.com/questions/22519784/how-do- i-convert-an-existing-callback-api-zu-versprechen (konvertieren Sie die Bibliothek selbst), [deshalb jQuery defereds sind schlecht] (http://stackoverflow.com/questions/23744612/problems-inhärente-to -jquery-deferred/23744774 # 23744774) und [wie von domenic erklärt] (https://gist.github.com/domenic/3889970), wie für die Bibliothek, würde ich [Bluebird] (https: // github .com/petkaantonov/bluebird) –

Antwort

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

Nein. Sie können 'reject' oder' resolve' nicht für eine Funktion aufrufen, die eine Versprechung zurückgibt. Tut mir leid, ich kann 'myFunc' nicht ändern, um zurückgegeben zu werden, anstatt zu versprechen ... – mnowotka

+0

Ah Entschuldigung, ich habe verpasst, dass Sie nur ein Versprechen hatten. Versuchen Sie die oben genannten erneut, ich denke, Proxying Callback sollte funktionieren. – jgillich

+0

macht Sinn, ich werde das überprüfen, danke! – mnowotka

2

Sie sollten immer auf der niedrigsten möglichen Stufe promsiify. Beginnen wir mit den Grundlagen.

Ich werde verwenden jQuery verspricht hier, aber dies sollte wirklich mit einer stärkeren Bibliothek wie Drossel Lassen Sie uns einfach anfangen, unsere delay wie durch die Schaffung erfolgen:

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

Hinweis Verzögerung tut nichts überraschend, Alle unsere Verzögerungsfunktion bewirkt eine Verzögerung von ms Millisekunden.

nun für Ihre Bibliothek wollen wir eine Version von doSomething, die mit dem Versprechen, Werke schaffen: beide nur eine Sache tun

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

Hinweis sowohl unsere Verzögerung und doSomethingAsync Funktionen. Jetzt beginnt der Spaß.

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

Jetzt in Bläuling, dieser ganzen Code wäre gewesen:

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

Ja, aber Sie könnten genauso gut 'timeout (myFunc(), 1000) schreiben. (...)' so verstehe ich nicht wirklich, warum Sie darauf bestehen, es durch 'doSomethingAsync' zu ersetzen. Abgesehen davon sollte es im 'promise.then (...)' Teil Code für den Umgang mit Fehlern geben und auch Fortschritte, aber ich verstehe, dass Sie es als Implementierungsdetail zurückgelassen haben. – mnowotka

+0

Was ist, wenn "Versprechen" abgelehnt wird? Was ist mit Fortschrittsbenachrichtigungen?Ich spüre eine verzögerte Antipattern :-) Sauberste Lösung wäre wahrscheinlich etwas wie 'Promise.race (versprechen, rejectAfterDelay (ms))'. – Bergi

+0

@Bergi Ja definitiv, sauberste Lösung wäre das, was ich zuletzt in Bluebird gesagt habe. Das Problem ist, dass jQuery keine '.race' Methode oder ähnliches hat, so dass es sehr hässlich wäre. –

2

Ich weiß, das ist 2 Jahre alt, aber im Fall, dass jemand die Antwort sucht ...

I denke, Benjamin war nah dran, dass du willst, dass deine Zeitüberschreitung getrennt behandelt wird, also beginnen wir mit seiner Verzögerungsfunktion.

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

Dann, wenn Sie vor dem Code ausgeführt, um warten wollen, können Sie die Methode, die Sie als Folge dieses Versprechens verzögert wollen nennen.

Das ist normalerweise das, was ich versuche, wenn ich nach einer Auffrischung suche (warum ich hier bin). Allerdings ging es nicht darum, die Ausführung zu verzögern, sondern um einen Fehler, wenn es zu lange dauerte. In diesem Fall werden die Dinge dadurch komplizierter, weil Sie nicht auf die Zeitüberschreitung warten müssen, wenn Sie dies nicht tun müssen. Sie können die beiden Versprechen also nicht einfach in ein "Wann" verpacken. Sieht so aus, als ob wir einen weiteren Deferred in der Mischung brauchen.(Siehe 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 it's 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; 
    }); 
} 

Wir dies rationalisieren, dass in diesem Fall zu wissen, der Master verschieben verwirft nur, wenn das Timeout zuerst eintritt und löst nur dann, wenn die functPromise zuerst verrechnet wird. Aus diesem Grund müssen wir functPromise nicht an die Master-Defer-Lösung übergeben, da es das einzige ist, das übergeben werden kann und wir immer noch im Bereich sind.

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

    // Call the potentially async funct and hold onto it's 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; 
    }); 
}