2014-11-01 8 views
24

Ich möchte eine Operation wiederholt mit einer zunehmenden Zeitüberschreitung zwischen den einzelnen Operationen ausführen, bis sie erfolgreich ist oder eine bestimmte Zeit abläuft. Wie strukturiere ich das mit Versprechen in Q?Versprechen: Wiederholen Sie den Vorgang, bis es erfolgreich ist?

+0

Wenn Sie einen rekursive Schnipsel mit nativer Versprechungen und max Wiederholungen wollen, dies überprüfen: https://stackoverflow.com/a/44577075/3032209 –

Antwort

24

die Antworten hier sind meiner Meinung nach alle wirklich kompliziert. Kos hat die richtige Idee, aber Sie können den Code verkürzen, indem mehr idiomatische Versprechen Code zu schreiben:

function retry(operation, delay) { 
    return operation().catch(function(reason) { 
     return Q.delay(delay).then(retry.bind(null, operation, delay * 2)); 
    }); 
} 

Und mit Kommentaren:

function retry(operation, delay) { 
    return operation(). // run the operation 
     catch(function(reason) { // if it fails 
     return Q.delay(delay). // delay 
       // retry with more time 
       then(retry.bind(null, operation, delay * 2)); 
    }); 
} 

Wenn Sie es zu einer Zeitüberschreitung nach einer gewissen Zeit wollen (sagen wir, 10 Sekunden, können Sie einfach tun:

var promise = retry(operation, 1000).timeout(10000); 

Diese Funktionalität direkt in Q gebaut wird, keine Notwendigkeit, es neu zu erfinden :)

+1

Das ist ein wirklich großartiger Vorschlag. Ich habe jedoch ein Problem damit gefunden. Selbst wenn das Zeitlimit abgelaufen ist, wird die Wiederholungsfunktion sich selbst aufrufen, bis sie erfolgreich ist, was offensichtlich unendlich sein könnte. Gibt es eine saubere Möglichkeit, die Versprechenskette zu unterbrechen, wenn die Zeitüberschreitung auftritt, so dass keine Handler mehr aufgerufen werden oder wir ein Flag setzen müssen oder gar das timeout() zugunsten eines nAttempts-Zählers abbrechen müssen, der bei jedem Aufruf erhöht wird Operation() und gegen ein Limit geprüft? – JHH

+0

@JHH Wenn Sie mich Bluebird verwenden lassen, würde ich die Löschung verwenden - andernfalls würden Sie eine spezielle Closure-Variable verwenden, um es zu simulieren - oder ein "Token" (Variable) in die Wiederholungen übergeben und auf false setzen. Also gibt es Wege aber keinen wirklich sauberen Weg mit Q. –

+1

Natürlich gibt es Wege. Ich löste es sehr einfach mit einem maxAttempts und einem Zähler und übersprang mit Timeout insgesamt. Meine Sorge ist mehr mit der Tatsache, dass die obige Antwort in dieser Hinsicht fehlerhaft ist, und wenn nicht vorsichtig, könnten Leute endlose Operationen hervorbringen, die im Laufe der Zeit signifikante CPU verbrauchen. – JHH

0

Ich habe folgende mit Promises/A +

function delayAsync(timeMs) 
{ 
    return new Promise(function(resolve){ 
     setTimeout(resolve, timeMs); 
    }); 
} 

//use an IIFE so we can have a private scope 
//to capture some state  
(function(){ 
    var a; 
    var interval = 1000; 
    a = function(){ 
     return doSomethingAsync() 
      .then(function(success){ 
       if(success) 
       { 
        return true; 
       } 
       return delayAsync(interval) 
         .then(function(){ 
          interval *= 2; 
         }) 
         .then(a()); 
      }); 
    }; 
    a(); 
})(); 

Ich bin sicher, dass (die mit Q sollten in Ordnung sein) Sie herausfinden können, wie nach einer maximalen Timeout der Klemme zu helfen.

+0

Meinen Sie rufen 'a' ? –

+0

@BenjaminGruenbaum Ja. Entschuldigung wegen der Auslassung. – spender

0
  1. Weisen Sie eine boolesche Variable für "alle Prozess-Timeouts" zu.
  2. Call-Fenster setTimeout, um diese Variable "falsch" nach dem "alle Prozess-Timeout" zu machen.
  3. Aufruf Versprechen Betrieb mit einem Timeout.
  4. Wenn es gelingt kein Problem.
  5. Wenn es fehlschlägt: Rufen Sie in dem Fehlerhandler des Versprechens erneut die Versprechungsfunktion mit einem erhöhten Timeout auf, wenn die boolesche Variable wahr ist.

Etwas wie folgt aus:

var goOn= true; 

setTimeout(function() { 
    goOn= false; 
}, 30000); // 30 seconds -- all process timeout 


var timeout = 1000; // 1 second 

(function() { 
    var helperFunction = function() { 

     callAsyncFunc().then(function() { 
      // success... 
     }, function() { 
      // fail 
      if (goOn) { 
       timeout += 1000; // increase timeout 1 second 
       helperFunction(); 
      } 
     }).timeout(timeout); 

    } 
})(); 
+0

Ich denke, Sie könnten verwirrt sein, was ".timeout" tut (es tut tatsächlich, was Sie 'goOn' verwenden, nur weil es in die Bibliothek eingebaut ist). –

2

Ich glaube, Sie nicht auf Versprechen Ebene tun können - ein Versprechen, keine Operation, sondern ist nur ein Wert, der in der Zukunft ankommen geht, Sie können also keine eingegebene Funktion Promise -> Promise definieren, die dies erreicht.

Sie müssten eine Ebene tiefer gehen und eine Funktion schreiben, die Operation -> Promise eingegeben wurde, wo die Operation selbst getippt wird () -> Promise. Ich nehme an, dass die Operation keine Parameter annimmt - Sie können sie vorher teilweise anwenden.

Hier ist eine rekursive Ansatz, der das Timeout auf jedem Lauf verdoppelt:

function RepeatUntilSuccess(operation, timeout) { 
    var deferred = Q.defer(); 
    operation().then(function success(value) { 
     deferred.resolve(value); 
    }, function error(reason) { 
     Q.delay(timeout 
     .then(function() { 
      return RepeatUntilSuccess(operation, timeout*2); 
     }).done(function(value) { 
      deferred.resolve(value); 
     }); 
    }); 
    return deferred.promise; 
} 

Demo: http://jsfiddle.net/0dmzt53p/

+2

Warum das aufgeschoben? Sie können die Verkettung von Versprechen verwenden. –

+0

Cool, danke, dass du es mir gezeigt hast! Ich lerne immer noch, dass ich am Ende nicht einfach genug bin. (Ich muss sie noch nicht draußen ausprobieren) – Kos

+0

Nun, ich [schrieb ein Q & A zu diesem speziellen Thema] (http://stackoverflow.com/questions/23803743/what-is-the-sefered-antipattern-and) (how-do-i-avoid-it) basiert auf Petkas (Link dort). –

3

Hier ist ein Beispiel, wie ich das mit einigen Hilfsfunktionen angehen würde. Beachten Sie, dass der 'maxTimeout' der kompliziertere Teil ist, weil Sie zwei Zustände rennen müssen.

// Helper delay function to wait a specific amount of time. 
function delay(time){ 
    return new Promise(function(resolve){ 
     setTimeout(resolve, time); 
    }); 
} 

// A function to just keep retrying forever. 
function runFunctionWithRetries(func, initialTimeout, increment){ 
    return func().catch(function(err){ 
     return delay(initialTimeout).then(function(){ 
      return runFunctionWithRetries(
        func, initialTimeout + increment, increment); 
     }); 
    }); 
} 

// Helper to retry a function, with incrementing and a max timeout. 
function runFunctionWithRetriesAndMaxTimeout(
     func, initialTimeout, increment, maxTimeout){ 

    var overallTimeout = delay(maxTimeout).then(function(){ 
     // Reset the function so that it will succeed and no 
     // longer keep retrying. 
     func = function(){ return Promise.resolve() }; 
     throw new Error('Function hit the maximum timeout'); 
    }); 

    // Keep trying to execute 'func' forever. 
    var operation = runFunctionWithRetries(function(){ 
     return func(); 
    }, initialTimeout, increment); 

    // Wait for either the retries to succeed, or the timeout to be hit. 
    return Promise.race([operation, overallTimeout]); 
} 

dann diese Helfer verwenden, können Sie so etwas tun würde:

// Your function that creates a promise for your task. 
function doSomething(){ 
    return new Promise(...); 
} 

runFunctionWithRetriesAndMaxTimeout(function(){ 
    return doSomething(); 
}, 1000 /* start at 1s per try */, 500 /* inc by .5s */, 30000 /* max 30s */); 
0

Sie können auch diese Open-Source-promise-retry component verwenden, die eine Funktion versucht erneut, die ein Versprechen zurückgibt.

Beispiel:

promiseRetry((retry, number) => promiseFunction().catch(retry),{retries:3}) 
.then((result) => console.log(result)).catch(err => console.log(err))