承诺重试devise模式

编辑

  1. 继续重试直到承诺解决的模式(使用delay和maxRetries)。
  2. 继续重试直到条件符合结果(具有延迟和maxRetries)的模式。
  3. 具有无限次重试的内存高效dynamic模式(提供延迟)。

代码为#1。 不断重试,直到承诺解决(语言等任何改进社区?)

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

使用

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

代码#2继续重试,直到一个条件满足, then以可重用的方式(条件是什么将会有所不同)。

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

有点不同…

asynchronous重试可以通过构build一个.catch()链来实现,而不是通常的.then()链。

这种做法是:

  • 只有在指定的最大尝试次数的情况下才可能。 (链必须是有限的长度),
  • 只适用于低的最大值。 (Promise链消耗的内存大致与其长度成正比)。

否则,使用recursion解决scheme。

首先,用作.catch()callback函数的效用函数。

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

现在您可以非常简洁地构build.catch链:

重试直到承诺解决,延迟

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

演示 : https : //jsfiddle.net/duL0qjqe/

重试直到结果满足一些条件,没有延迟

 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/

重试直到结果满足某些条件,并延迟

考虑到(1)和(2),综合testing+延迟同样微不足道。

 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()可以是同步的或asynchronous的。

增加进一步的testing也是微不足道的。 简单地夹在两个渔获物之间的​​一串链。

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

演示 : https : //jsfiddle.net/duL0qjqe/3/


所有版本都是为了attempt成为承诺返回asynchronous函数而devise的。 也可以想象返回一个值,在这种情况下,链将沿着它的成功path到达下一个/terminal。

你可以把一个新的承诺链接到前一个承诺,从而推迟其最终的解决scheme,直到你知道最终的答案。 如果下一个答案仍然是未知的,那么链接另一个答案,并保持链接checkStatus()自己,直到最终你知道答案,并可以返回最后的决议。 这可以这样工作:

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

请注意,我也避免在switch语句中创buildpromise。 既然你已经在一个.then()处理程序中,只要返回一个值就是parsing,抛出一个exception就是拒绝,而返回一个promise就是将一个新的承诺链接到前一个。 这涵盖了switch语句的三个分支,而不是在那里创build一个新的promise。 为了方便起见,我使用了基于promise的delay()函数。

仅供参考,这假定work.requestStatus()不需要任何参数。 如果确实需要一些特定的参数,那么可以在函数调用的时候传递这些参数。


实现某种超时值也许是一个好主意,因为你将循环等待完成多久,所以这永远不会永远持续下去。 你可以像这样添加超时function:

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

我不确定你正在寻找什么“devise模式”。 既然你似乎反对外部声明的checkStatus()函数,下面是一个内联版本:

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

在许多情况下可以使用的更可重用的重试scheme将定义一些可重用的外部代码,但是您似乎反对,因此我没有提供该版本。


这里有一个方法是根据你的请求在Promise.prototype上使用一个.retryUntil()方法。 如果你想调整这个实现细节,你应该能够修改这个一般的方法:

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

我仍然无法确切地告诉你想要什么,或者所有这些方法都不能解决你的问题。 由于你的方法似乎都是内联代码而不是使用可重用的帮手,下面是其中的一个:

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

2.继续重试直到条件符合结果的模式 (带有delay和maxRetries)

这是一个很好的方式来做到这一点与原生承诺以recursion的方式:

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

这就是你如何调用它,假设func有时成功,有时失败,总是返回一个我们可以logging的string:

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

这里我们调用retryOperation,要求它每秒重试一次,最大重试次数= 5。

如果你想要一些没有承诺的简单的东西,RxJs会帮助你: https : //github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/retrywhen.md

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