Modifier
Code pour # 1. Continue à réessayer jusqu'à ce que promesse soit résolue (toute communauté d'améliorations pour la langue, etc.?)
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();
});
};
Utilisation
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 pour # 2 continuez de réessayer jusqu'à ce qu'une condition se rencontre sur le résultat then
de manière réutilisable (la condition est ce qui variera).
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);
Quelque chose d'un peu différent ...
Les tentatives asynchrones peuvent être réalisées en créant une chaîne .catch()
, par opposition à la chaîne plus habituelle .then()
.
Cette approche est:
Sinon, utilisez une solution récursive.
Tout d'abord, une fonction utilitaire à utiliser comme rappel .catch()
.
var t = 500;
function rejectDelay(reason) {
return new Promise(function(resolve, reject) {
setTimeout(reject.bind(null, reason), t);
});
}
Maintenant, vous pouvez construire des chaînes .catch de manière très concise:
1. Réessayer jusqu'à ce que la promesse soit résolue, avec délai
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. Réessayer jusqu'à ce que le résultat réponde à une condition, sans délai
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. Réessayer jusqu'à ce que le résultat réponde à une condition, avec délai
Après avoir réfléchi aux questions (1) et (2), un test combiné + délai est également trivial.
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()
peut être synchrone ou asynchrone.
Il serait également trivial d'ajouter des tests supplémentaires. Il suffit de mettre en sandwich une chaîne de thens entre les deux prises.
p = p.catch(attempt).then(test1).then(test2).then(test3).catch(rejectDelay);
DEMO: https://jsfiddle.net/duL0qjqe/3/
Toutes les versions sont conçues pour que attempt
soit une fonction asynchrone à retour de promesse. Il pourrait aussi éventuellement renvoyer une valeur, auquel cas la chaîne suivrait son chemin de réussite jusqu'au prochain/terminal .then()
.
2. Modèle qui réessaie jusqu'à ce que la condition remplie le résultat (avec délai et maxRetries)
C'est une bonne manière de faire cela avec des promesses autochtones de manière récursive:
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);
});
});
Voici comment vous l'appelez, en supposant que func
réussit parfois et échoue parfois, en renvoyant toujours une chaîne que nous pouvons consigner:
retryOperation(func, 1000, 5)
.then(console.log)
.catch(console.log);
Ici, nous appelons retryOperation pour lui demander de réessayer toutes les secondes et avec un nombre maximal de tentatives = 5.
Si vous voulez quelque chose de plus simple sans promesses, RxJs pourrait vous aider: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/retrywhen.md
Vous pouvez chaîner une nouvelle promesse sur la précédente, retardant ainsi sa résolution éventuelle jusqu'à ce que vous connaissiez la réponse finale. Si la réponse suivante n'est toujours pas connue, enchaînez une autre promesse et continuez à enchaîner checkStatus () jusqu'à ce que vous connaissiez la réponse et puissiez renvoyer la résolution finale. Cela pourrait fonctionner comme ceci:
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);
Remarque, j'ai également évité de créer la promesse autour de votre déclaration switch
. Puisque vous êtes déjà dans un gestionnaire .then()
, le simple renvoi d'une valeur est une résolution, le lancement d'une exception est un rejet et le renvoi d'une promesse consiste à chaîner une nouvelle promesse sur la précédente. Cela couvre les trois branches de votre déclaration switch
sans créer de nouvelle promesse. Pour plus de commodité, j'utilise une fonction delay()
basée sur des promesses.
Pour votre information, cela suppose que work.requestStatus()
n'a besoin d'aucun argument. Si des arguments spécifiques sont nécessaires, vous pouvez les transmettre au moment de l’appel de la fonction.
Il peut également être judicieux d’implémenter une sorte de valeur de délai d’attente pendant la durée de la boucle d’attente, de sorte que cela ne dure jamais éternellement. Vous pouvez ajouter la fonctionnalité de délai d'attente comme ceci:
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);
Je ne sais pas exactement quel "modèle de conception" vous recherchez. Puisque vous semblez vous opposer à la fonction déclarée en externe checkStatus()
, voici une version intégrée:
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);
Un schéma de réessai plus réutilisable qui pourrait être utilisé dans de nombreuses circonstances définirait un code externe réutilisable, mais vous semblez vous opposer à cela et je n'ai donc pas créé cette version.
Voici une autre approche qui utilise une méthode .retryUntil()
sur le Promise.prototype
selon votre demande. Si vous souhaitez modifier les détails de mise en œuvre de cette opération, vous devriez pouvoir modifier cette approche générale:
// 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);
Je ne peux toujours pas vraiment dire ce que vous voulez ou ce que toutes ces approches ne résout pas. Étant donné que vos approches semblent toutes être du code en ligne et ne pas utiliser d’aide fiable, voici l’une de celles-ci:
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);
Il y a beaucoup de bonnes solutions mentionnées et maintenant, avec async/wait, ces problèmes peuvent être résolus sans effort.
Si une approche récursive ne vous dérange pas, voici ma solution.
function retry(fn, retries=3, err=null) {
if (!retries) {
return Promise.reject(err);
}
return fn().catch(err => {
return retry(fn, (retries - 1), err);
});
}
Une bibliothèque peut le faire facilement: promise-retry .
Voici quelques exemples pour le tester:
const promiseRetry = require('promise-retry');
Attendez-vous à ce que la deuxième tentative réussisse:
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);
});
});
Attendez-vous à une nouvelle tentative:
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();
});
});
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);