webentwicklung-frage-antwort-db.com.de

Versprechen Sie erneut, Design Patterns

Bearbeiten

  1. Muster, die so lange wiederholt werden, bis das Versprechen aufgelöst wird (mit Verzögerung und MaxRetries). 
  2. Muster, das wiederholt wird, bis die Bedingung .__ auf das Ergebnis (mit Verzögerung und MaxRetries) erfüllt ist.
  3. Ein speichereffizientes dynamisches Pattern mit unbegrenzten Wiederholungsversuchen (Verzögerung vorhanden).

Code für # 1. Wiederholt sich solange, bis das Versprechen gelöst wird (Verbesserungs-Community für die Sprache usw.)

Promise.retry = function(fn, times, delay) {
    return new Promise(function(resolve, reject){
        var error;
        var attempt = function() {
            if (times == 0) {
                reject(error);
            } else {
                fn().then(resolve)
                    .catch(function(e){
                        times--;
                        error = e;
                        setTimeout(function(){attempt()}, delay);
                    });
            }
        };
        attempt();
    });
};

Benutzen

work.getStatus()
    .then(function(result){ //retry, some glitch in the system
        return Promise.retry(work.unpublish.bind(work, result), 10, 2000);
    })
    .then(function(){console.log('done')})
    .catch(console.error);

Code für # 2 Wiederholen Sie den Vorgang so lange, bis eine Bedingung auf das Ergebnis der then wiederverwendbar ist (die Bedingung variiert).

work.publish()
    .then(function(result){
        return new Promise(function(resolve, reject){
            var intervalId = setInterval(function(){
                work.requestStatus(result).then(function(result2){
                    switch(result2.status) {
                        case "progress": break; //do nothing
                        case "success": clearInterval(intervalId); resolve(result2); break;
                        case "failure": clearInterval(intervalId); reject(result2); break;
                    }
                }).catch(function(error){clearInterval(intervalId); reject(error)});
            }, 1000);
        });
    })
    .then(function(){console.log('done')})
    .catch(console.error);
32
user2727195

Etwas anders ...

Async-Wiederholungen können durch Erstellen einer .catch()-Kette im Gegensatz zu der üblicheren .then()-Kette erreicht werden.

Dieser Ansatz ist:

  • nur bei einer angegebenen maximalen Anzahl von Versuchen möglich. (Die Kette muss eine endliche Länge haben)
  • nur mit niedrigem Maximum empfehlenswert. (Promise-Ketten verbrauchen den Speicher ungefähr proportional zu ihrer Länge).

Verwenden Sie andernfalls eine rekursive Lösung.

Zuerst eine Utility-Funktion, die als .catch()-Callback verwendet werden soll.

var t = 500;

function rejectDelay(reason) {
    return new Promise(function(resolve, reject) {
        setTimeout(reject.bind(null, reason), t); 
    });
}

Jetzt können Sie .catch-Ketten sehr kurz bauen:

1. Wiederholen Sie den Vorgang, bis das Versprechen mit Verzögerung abgelöst wird

var max = 5;
var p = Promise.reject();

for(var i=0; i<max; i++) {
    p = p.catch(attempt).catch(rejectDelay);
}
p = p.then(processResult).catch(errorHandler);

DEMO: https://jsfiddle.net/duL0qjqe/

2. Wiederholen Sie den Vorgang, bis das Ergebnis eine Bedingung ohne Verzögerung erfüllt.

var max = 5;
var p = Promise.reject();

for(var i=0; i<max; i++) {
    p = p.catch(attempt).then(test);
}
p = p.then(processResult).catch(errorHandler);

DEMO: https://jsfiddle.net/duL0qjqe/1/

3. Wiederholen Sie den Vorgang, bis das Ergebnis eine bestimmte Bedingung erfüllt, mit Verzögerung

Wenn Sie Ihre Gedanken (1) und (2) durchgedacht haben, ist ein kombinierter Test + Delay ebenso unbedeutend.

var max = 5;
var p = Promise.reject();

for(var i=0; i<max; i++) {
    p = p.catch(attempt).then(test).catch(rejectDelay);
    // Don't be tempted to simplify this to `p.catch(attempt).then(test, rejectDelay)`. Test failures would not be caught.
}
p = p.then(processResult).catch(errorHandler);

test() kann synchron oder asynchron sein.

Es wäre auch trivial, weitere Tests hinzuzufügen. Legen Sie einfach eine Thens-Kette zwischen die beiden Verschlüsse.

p = p.catch(attempt).then(test1).then(test2).then(test3).catch(rejectDelay);

DEMO: https://jsfiddle.net/duL0qjqe/3/


Alle Versionen sind so konzipiert, dass attempt eine async-Funktion mit Versprechen ist. Es ist auch denkbar, dass ein Wert zurückgegeben wird, in welchem ​​Fall die Kette ihrem Erfolgspfad zum nächsten/terminal .then() folgen würde.

30
Roamer-1888

2. Muster, das wiederholt wird, bis die Bedingung auf das Ergebnis trifft (mit Verzögerung und maxRetries)

Dies ist ein schöner Weg, um dies mit native Versprechen auf rekursive Weise zu tun:

const wait = ms => new Promise(r => setTimeout(r, ms));

const retryOperation = (operation, delay, times) => new Promise((resolve, reject) => {
  return operation()
    .then(resolve)
    .catch((reason) => {
      if (times - 1 > 0) {
        return wait(delay)
          .then(retryOperation.bind(null, operation, delay, times - 1))
          .then(resolve)
          .catch(reject);
      }
      return reject(reason);
    });
});

So nennen Sie es, vorausgesetzt, dass func manchmal erfolgreich ist und manchmal fehlschlägt und immer eine Zeichenfolge zurückgibt, die wir protokollieren können:

retryOperation(func, 1000, 5)
  .then(console.log)
  .catch(console.log);

Hier rufen wir retryOperation auf und bitten Sie, jede Sekunde und mit max retries = 5 erneut zu versuchen.

Wenn Sie etwas Einfacheres ohne Versprechen wünschen, könnten RxJs dabei helfen: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/retrywhen.md

14
Yair Kukielka

Sie können ein neues Versprechen an das vorherige anketten, wodurch die endgültige Auflösung verzögert wird, bis Sie die endgültige Antwort kennen. Wenn die nächste Antwort noch nicht bekannt ist, ketten Sie ein weiteres Versprechen darauf und verketten Sie checkStatus () mit sich selbst, bis Sie schließlich die Antwort kennen und die endgültige Auflösung zurückgeben können. Das könnte so funktionieren:

function delay(t) {
    return new Promise(function(resolve) {
        setTimeout(resolve, t);
    });
}

function checkStatus() {
    return work.requestStatus().then(function(result) {
        switch(result.status) {
            case "success":
                return result;      // resolve
            case "failure":
                throw result;       // reject
            case default:
            case "inProgress": //check every second
                return delay(1000).then(checkStatus);
        }
    });
}

work.create()
    .then(work.publish) //remote work submission
    .then(checkStatus)
    .then(function(){console.log("work published"})
    .catch(console.error);

Beachten Sie, ich habe auch vermieden, das Versprechen um Ihre switch-Anweisung herum zu erstellen. Da Sie sich bereits in einem .then()-Handler befinden, ist die Rückgabe eines Wertes durch die Rückgabe eines Ausnahmebedarfs die Zurückweisung und die Rückgabe eines Versprechens ein neues Versprechen an das vorherige. Das deckt die drei Zweige Ihrer switch-Anweisung ab, ohne dort ein neues Versprechen zu schaffen. Der Einfachheit halber verwende ich eine delay()-Funktion, die auf Versprechungen basiert.

Zu Ihrer Information, dies setzt voraus, dass work.requestStatus() keine Argumente benötigt. Wenn bestimmte Argumente erforderlich sind, können Sie diese am Punkt des Funktionsaufrufs übergeben.


Es kann auch eine gute Idee sein, eine Art Zeitüberschreitungswert zu implementieren, für den Zeitraum, in dem Sie die Schleife abwarten, bis der Abschluß abgeschlossen ist. Sie können die Timeout-Funktion folgendermaßen hinzufügen:

function delay(t) {
    return new Promise(function(resolve) {
        setTimeout(resolve, t);
    });
}

function checkStatus(timeout) {
    var start = Date.now();

    function check() {
        var now = Date.now();
        if (now - start > timeout) {
            return Promise.reject(new Error("checkStatus() timeout"));
        }
        return work.requestStatus().then(function(result) {
            switch(result.status) {
                case "success":
                    return result;      // resolve
                case "failure":
                    throw result;       // reject
                case default:
                case "inProgress": //check every second
                    return delay(1000).then(check);
            }
        });
    }
    return check;
}

work.create()
    .then(work.publish) //remote work submission
    .then(checkStatus(120 * 1000))
    .then(function(){console.log("work published"})
    .catch(console.error);

Ich weiß nicht genau, nach welchem ​​"Designmuster" Sie suchen. Da Sie der extern deklarierten checkStatus()-Funktion scheinbar widersprechen, ist hier eine Inline-Version:

work.create()
    .then(work.publish) //remote work submission
    .then(work.requestStatus)
    .then(function() {
        // retry until done
        var timeout = 10 * 1000;
        var start = Date.now();

        function check() {
            var now = Date.now();
            if (now - start > timeout) {
                return Promise.reject(new Error("checkStatus() timeout"));
            }
            return work.requestStatus().then(function(result) {
                switch(result.status) {
                    case "success":
                        return result;      // resolve
                    case "failure":
                        throw result;       // reject
                    case default:
                    case "inProgress": //check every second
                        return delay(1000).then(check);
                }
            });
        }
        return check();
    }).then(function(){console.log("work published"})
    .catch(console.error);

Ein wiederverwendbares Wiederholungsschema, das unter vielen Umständen verwendet werden könnte, würde einen wiederverwendbaren externen Code definieren, aber Sie scheinen dem zu widersprechen, daher habe ich diese Version nicht erstellt.


Hier ist ein weiterer Ansatz, der je nach Anforderung eine .retryUntil()-Methode für den Promise.prototype verwendet. Wenn Sie die Implementierungsdetails optimieren möchten, sollten Sie diesen allgemeinen Ansatz ändern können:

// fn returns a promise that must be fulfilled with an object
//    with a .status property that is "success" if done.  Any
//    other value for that status means to continue retrying
//  Rejecting the returned promise means to abort processing 
//        and propagate the rejection
// delay is the number of ms to delay before trying again
//     no delay before the first call to the callback
// tries is the max number of times to call the callback before rejecting
Promise.prototype.retryUntil = function(fn, delay, tries) {
    var numTries = 0;
    function check() {
        if (numTries >= tries) {
            throw new Error("retryUntil exceeded max tries");
        }
        ++numTries;
        return fn().then(function(result) {
            if (result.status === "success") {
                return result;          // resolve
            } else {
                return Promise.delay(delay).then(check);
            }
        });
    }
    return this.then(check);
}

if (!Promise.delay) {
    Promise.delay = function(t) {
        return new Promise(function(resolve) {
            setTimeout(resolve, t);
        });
    }
}


work.create()
    .then(work.publish) //remote work submission
    .retryUntil(function() {
        return work.requestStatus().then(function(result) {
            // make this promise reject for failure
            if (result.status === "failure") {
                throw result;
            }
            return result;
        })
    }, 2000, 10).then(function() {
        console.log("work published");
    }).catch(console.error);

Ich kann immer noch nicht wirklich sagen, was Sie wollen oder was bei all diesen Ansätzen nicht Ihr Problem löst. Da es sich bei Ihren Ansätzen alle um Inline-Code handelt und kein unterstützbarer Helfer verwendet wird, haben Sie folgende Möglichkeiten:

work.create()
    .then(work.publish) //remote work submission
    .then(function() {
        var tries = 0, maxTries = 20;
        function next() {
            if (tries > maxTries) {
                throw new Error("Too many retries in work.requestStatus");
            }
            ++tries;
            return work.requestStatus().then(function(result) {
                switch(result.status) {
                    case "success":
                        return result;
                    case "failure":
                        // if it failed, make this promise reject
                        throw result;
                    default:
                        // for anything else, try again after short delay
                        // chain to the previous promise
                        return Promise.delay(2000).then(next);
                }

            });
        }
        return next();
    }).then(function(){
        console.log("work published")
    }).catch(console.error);
8
jfriend00

Es gibt viele gute Lösungen, die jetzt mit async/await gelöst werden können.

Wenn Sie sich nicht für einen rekursiven Ansatz interessieren, ist dies meine Lösung.

function retry(fn, retries=3, err=null) {
  if (!retries) {
    return Promise.reject(err);
  }
  return fn().catch(err => {
      return retry(fn, (retries - 1), err);
    });
}
6
holmberd

async-retry.ts versucht, das Muster zu implementieren, ich verwende es in der Produktion für einige Projekte.

Installation:

npm installiere async-retry.ts --save

Verwendungszweck:

import Action from 'async-retry.ts'

const action = async()=>{}
const handlers = [{
  error: 'error1',
  handler: async yourHandler1()=>{}
}, {
  error: 'error2',
  handler: async yourHandler2()=>{}
}]

await Action.retryAsync(action, 3, handlers)

Dieses Paket ist ziemlich neu, aber es ist von einem langlebigen Paket abgeleitet co-retry welches die retry pattern in Generatorfunktion.

1
Jeff Tian

Eine Bibliothek kann dies problemlos tun: versprechen-erneut versuchen .

Hier einige Beispiele zum Testen:

const promiseRetry = require('promise-retry');

Erwarten Sie den zweiten Versuch, erfolgreich zu sein:

it('should retry one time after error', (done) => {
    const options = {
        minTimeout: 10,
        maxTimeout: 100
    };
    promiseRetry((retry, number) => {
        console.log('test2 attempt number', number);
        return new Promise((resolve, reject) => {
            if (number === 1) throw new Error('first attempt fails');
            else resolve('second attempt success');
        }).catch(retry);
    }, options).then(res => {
        expect(res).toBe('second attempt success');
        done();
    }).catch(err => {
        fail(err);
    });
});

Erwarten Sie nur eine Wiederholung:

it('should not retry a second time', (done) => {
    const options = {
        retries: 1,
        minTimeout: 10,
        maxTimeout: 100
    };
    promiseRetry((retry, number) => {
        console.log('test4 attempt number', number);
        return new Promise((resolve, reject) => {
            if (number <= 2) throw new Error('attempt ' + number + ' fails');
            else resolve('third attempt success');
        }).catch(retry);
    }, options).then(res => {
        fail('Should never success');
    }).catch(err => {
        expect(err.toString()).toBe('Error: attempt 2 fails');
        done();
    });
});
0
Bludwarf

Hier ist eine exponentielle Backoff-Neuversuchsimplementierung unter Verwendung von async/await, die jede Versprechen-API einschließen kann. Es simuliert einen schuppigen Endpunkt mit Mathe-Zufall. Versuchen Sie also einige Male, sowohl Erfolgs- als auch Misserfolgsfälle zu erkennen.

/**
 * Wrap a promise API with a function that will attempt the promise over and over again
 * with exponential backoff until it resolves or reaches the maximum number of retries.
 *   - First retry: 500 ms + <random> ms
 *   - Second retry: 1000 ms + <random> ms
 *   - Third retry: 2000 ms + <random> ms
 * and so forth until maximum retries are met, or the promise resolves.
 */
const withRetries = ({ attempt, maxRetries }) => async (...args) => {
  const slotTime = 500;
  let retryCount = 0;
  do {
    try {
      console.log('Attempting...', Date.now());
      return await attempt(...args);
    } catch (error) {
      const isLastAttempt = retryCount === maxRetries;
      if (isLastAttempt) {
        // Stack Overflow console doesn't show unhandled
        // promise rejections so lets log the error.
        console.error(error);
        return Promise.reject(error);
      }
    }
    const randomTime = Math.floor(Math.random() * slotTime);
    const delay = 2 ** retryCount * slotTime + randomTime;
    // Wait for the exponentially increasing delay period before retrying again.
    await new Promise(resolve => setTimeout(resolve, delay));
  } while (retryCount++ < maxRetries);
}

const fakeAPI = (arg1, arg2) => Math.random() < 0.25 ? Promise.resolve(arg1) : Promise.reject(new Error(arg2))
const fakeAPIWithRetries = withRetries({ attempt: fakeAPI, maxRetries: 3 });
fakeAPIWithRetries('arg1', 'arg2').then(results => console.log(results))
0
Red Mercury
work.create()
    .then(work.publish) //remote work submission
    .then(function(result){
        var maxAttempts = 10;
        var handleResult = function(result){
            if(result.status === 'success'){
                return result;
            }
            else if(maxAttempts <= 0 || result.status === 'failure') {
                return Promise.reject(result);
            }
            else {
                maxAttempts -= 1;
                return (new Promise( function(resolve) {
                    setTimeout( function() {
                        resolve(_result);
                    }, 1000);
                })).then(function(){
                    return work.requestStatus().then(handleResult);
                });
            }
        };
        return work.requestStatus().then(handleResult);
    })
    .then(function(){console.log("work published"})
    .catch(console.error);
0
Hugo Silva