承诺重试devise模式
编辑
- 继续重试直到承诺解决的模式(使用delay和maxRetries)。
- 继续重试直到条件符合结果(具有延迟和maxRetries)的模式。
- 具有无限次重试的内存高效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);