承诺重试设计模式
Promise Retry Design Patterns
>Edit
- 不断重试直到承诺解决的模式(使用
delay
和maxRetries
(。 - 不断重试直到条件出现的模式根据结果满足(
delay
和maxRetries
(。 - 具有无限次重试的内存高效动态模式(提供
delay
(。
#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);
有点不同...
异步重试可以通过构建.catch()
链来实现,而不是更常见的.then()
链。
这种方法是:
- 仅当达到
- 指定的最大尝试次数时才能使用。(链条长度必须有限(,
- 仅建议使用较低的最大值。(承诺链消耗的内存与其长度大致成正比(。
否则,请使用递归解决方案。
首先,用作.catch()
回调的实用程序函数。
var t = 500;
function rejectDelay(reason) {
return new Promise(function(resolve, reject) {
setTimeout(reject.bind(null, reason), t);
});
}
现在你可以非常简洁地构建 .catch 链:
1. 重试,直到承诺解决,延迟
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/
2. 重试,直到结果满足某些条件,不要延迟
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);
演示:https://jsfiddle.net/duL0qjqe/1/
3. 重试,直到结果满足某些条件,并延迟
在考虑了(1(和(2(之后,组合测试+延迟同样微不足道。
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()
可以是同步的,也可以是异步的。
添加进一步的测试也是微不足道的。只需在两个渔获物之间夹上一连串即可。
p = p.catch(attempt).then(test1).then(test2).then(test3).catch(rejectDelay);
演示:https://jsfiddle.net/duL0qjqe/3/
所有版本都旨在attempt
成为返回承诺的异步函数。它还可以返回一个值,在这种情况下,链将遵循其成功路径到达下一个/终端.then()
。
2. 不断重试直到条件满足结果的模式(具有延迟和最大重试次数(
这是以递归方式使用本机承诺执行此操作的好方法:
const wait = ms => new Promise(r => setTimeout(r, ms));
const retryOperation = (operation, delay, retries) => new Promise((resolve, reject) => {
return operation()
.then(resolve)
.catch((reason) => {
if (retries > 0) {
return wait(delay)
.then(retryOperation.bind(null, operation, delay, retries - 1))
.then(resolve)
.catch(reject);
}
return reject(reason);
});
});
这就是你如何调用它,假设func
有时成功,有时失败,总是返回一个我们可以记录的字符串:
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
提到了许多很好的解决方案,现在使用 async/await 可以毫不费力地解决这些问题。
如果您不介意递归方法,那么这就是我的解决方案。
function retry(fn, retries=3, err=null) {
if (!retries) {
return Promise.reject(err);
}
return fn().catch(err => {
return retry(fn, (retries - 1), err);
});
}
您可以将新承诺链接到前一个承诺上,从而延迟其最终解决方案,直到您知道最终答案。 如果下一个答案仍然未知,那么在它上面链接另一个承诺,并继续将 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
声明创建承诺。 由于你已经在 .then()
处理程序中,因此只需返回一个值即可解决,引发异常是拒绝,返回承诺会将新承诺链接到前一个承诺。 这涵盖了你switch
声明的三个分支,而没有在其中创建新的承诺。 为了方便起见,我确实使用了一个基于 promise 的delay()
函数。
仅供参考,这假设work.requestStatus()
不需要任何参数。 如果它确实需要一些特定的参数,你可以在函数调用时传递这些参数。
实现某种超时值来表示您将循环等待完成的时间,这样这也不会永远持续下去。 您可以添加如下超时功能:
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);
我不确定您正在寻找的确切"设计模式"。 由于您似乎反对外部声明的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);
在许多情况下可以使用的更可重用的重试方案将定义一些可重用的外部代码,但您似乎反对这样做,所以我没有制作该版本。
下面是另一种方法,它根据你的请求在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);
<</div>
div class="answers"> 这是一个"指数退避"重试实现,使用可以包装任何承诺 API 的async/await
。
注意:出于演示原因,代码段使用 Math.random
模拟片状端点,因此请尝试几次以查看成功和失败案例。
/**
* 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))
Check @jsier/retrier。经过测试,记录,轻量级,易于使用,没有外部依赖关系,并且已经在生产中已有一段时间了。
支持:
- 首次尝试延迟
- 尝试之间的延迟
- 限制尝试次数
- 回调以在满足某些条件时停止重试(例如,遇到特定错误(
- 回调以在满足某些条件时继续重试(例如,解析的值不令人满意(
安装:
npm install @jsier/retrier
用法:
import { Retrier } from '@jsier/retrier';
const options = { limit: 5, delay: 2000 };
const retrier = new Retrier(options);
retrier
.resolve(attempt => new Promise((resolve, reject) => reject('Dummy reject!')))
.then(
result => console.log(result),
error => console.error(error) // After 5 attempts logs: "Dummy reject!"
);
包没有外部依赖项。
的尝试。我试图从上述所有答案中获取我喜欢的东西。没有外部依赖关系。打字稿 + 异步/等待 (ES2017(
export async function retryOperation<T>(
operation: () => (Promise<T> | T), delay: number, times: number): Promise<T> {
try {
return await operation();
} catch (ex) {
if (times > 1) {
await new Promise((resolve) => setTimeout(resolve, delay));
return retryOperation(operation, delay, times - 1);
} else {
throw ex;
}
}
}
用法:
function doSomething() {
return Promise.resolve('I did something!');
}
const retryDelay = 1000; // 1 second
const retryAttempts = 10;
retryOperation(doSomething, retryDelay, retryAttempts)
.then((something) => console.log('I DID SOMETHING'))
.catch((err) => console.error(err));
在 holmberd 的解决方案基础上构建
,代码更简洁,延迟// Retry code
const wait = ms => new Promise((resolve) => {
setTimeout(() => resolve(), ms)
})
const retryWithDelay = async (
fn, retries = 3, interval = 50,
finalErr = Error('Retry failed')
) => {
try {
await fn()
} catch (err) {
if (retries <= 0) {
return Promise.reject(finalErr);
}
await wait(interval)
return retryWithDelay(fn, (retries - 1), interval, finalErr);
}
}
// Test
const getTestFunc = () => {
let callCounter = 0
return async () => {
callCounter += 1
if (callCounter < 5) {
throw new Error('Not yet')
}
}
}
const test = async () => {
await retryWithDelay(getTestFunc(), 10)
console.log('success')
await retryWithDelay(getTestFunc(), 3)
console.log('will fail before getting here')
}
test().catch(console.error)
如果你的代码被放在一个类中,你可以使用装饰器来实现这一点。你在实用程序装饰器(npm install --save utils-decorators
(库中有这样的装饰器:
import {retry} from 'utils-decorators';
class SomeService {
@retry(3)
doSomeAsync(): Promise<any> {
....
}
}
或者你可以使用包装器函数:
import {retryfy} from 'utils-decorators';
const withRetry = retryfy(originalFunc, 3);
注意:此库是树可摇动的,因此您不会为此库中的其余可用装饰器支付额外的字节。
https://github.com/vlio20/utils-decorators#retry-method
这里有很多答案,但经过一些研究,我决定采用递归方法。我将我的解决方案留给任何感兴趣的人
function retry(fn, retriesLeft = 2, interval = 1000) {
return new Promise((resolve, reject) => {
fn()
.then(resolve)
.catch((error) => {
if (retriesLeft === 0) {
reject(error);
return;
}
setTimeout(() => {
console.log('retrying...')
retry(fn, retriesLeft - 1, interval).then(resolve).catch(reject);
}, interval);
});
});
}
这是一个带有漂亮游乐场的堆叠闪电战,您可以在那里感受它的工作原理。只需绕过 intent 变量即可查看承诺解析/拒绝
https://js-vjramh.stackblitz.io
不知道为什么提出的所有解决方案都是递归的。使用 TypeScript 的迭代解决方案,它等到方法返回未定义的内容:
function DelayPromise(delayTime): Promise<void> {
return new Promise<void>((resolve) => setTimeout(resolve, delayTime));
}
interface RetryOptions {
attempts?: number;
delayMs?: number;
}
export async function retryOperation<T>(
operation: (attempt: number) => Promise<T>,
options: RetryOptions = {}
): Promise<T> {
const { attempts = 6, delayMs = 10000 } = options;
for (let i = 0; i < attempts; i++) {
const result = await operation(i);
if (typeof result !== 'undefined') {
return result;
}
await DelayPromise(delayMs);
}
throw new Error('Timeout');
}
function TryToSuccess(fun, reties) {
let attempt = 0;
let doTry = (...args) => {
attempt++;
return fun(...args)
.catch((err) => {
console.log("fail ", attempt);
if(attempt <= reties){
return doTry(...args);
} else {
return Promise.reject(err);
}
});
}
return doTry;
}
function asyncFunction(){
return new Promise((resolve, reject) => {
setTimeout(() => {
(window.findResult === true) ? resolve("Done") : reject("fail");
}, 2000);
});
}
var cloneFunc = TryToSuccess(asyncFunction, 3);
cloneFunc()
.then(res => {
console.log("Got Success. ", res)
})
.catch(err => {
console.log("Rejected with err ", err);
});
setTimeout(() => {
window.findResult = true;
}, 4000);
async-retry.ts 正在尝试实现该模式,我正在生产中将其用于某些项目。
安装:
npm install async-retry.ts --save
用法:
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)
这个包是相当新的,但它派生自一个长期存在的包co-retry
它以生成器函数的方式实现了retry pattern
。
这是我的解决方案:
- 使用打字稿保留函数类型。
- 接受具有任何参数的函数。
- 自定义
maxRetries
数。 - 自定义延迟行为
type AnyFn = (...any: any[]) => any;
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
type DelayFn = (retry: number) => number;
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export function retry<Fn extends AnyFn>(
fn: Fn,
maxRetries: number,
getDelay: DelayFn = () => 5000
) {
let retries = 0;
return async function wrapped(
...args: Parameters<Fn>
): Promise<Awaited<ReturnType<Fn>>> {
try {
return await fn(...args);
} catch (e) {
if (++retries > maxRetries) throw e;
const delayTime = getDelay(retries);
console.error(e);
console.log(`Retry ${fn.name} ${retries} times after delaying ${delayTime}ms`);
await delay(delayTime);
return await wrapped(...args);
}
};
}
用法
const badFn = () => new Promise((resolve, reject) => reject('Something is wrong');
const fn = retry(badFn, 5, (retry) => 2 ** retry * 1000);
fn();
// Something is wrong
// Retry badFn 1 times after delaying 2000ms
// Something is wrong
// Retry badFn 2 times after delaying 4000ms
// Something is wrong
// Retry badFn 3 times after delaying 8000ms
// Something is wrong
// Retry badFn 4 times after delaying 16000ms
// Something is wrong
// Retry badFn 5 times after delaying 32000ms
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function retry(fn, maxRetries, getDelay = () => 5000) {
let retries = 0;
return async function wrapped(...args) {
try {
return await fn(...args);
} catch (e) {
if (++retries > maxRetries) throw e;
const delayTime = getDelay(retries);
console.error(e);
console.log(`Retry ${fn.name} ${retries} times after delaying ${delayTime}ms`);
await delay(delayTime);
return await wrapped(...args);
}
};
}
const badFn = () => new Promise((resolve, reject) => reject('Something is wrong'));
const fn = retry(badFn, 5, (retry) => 2 ** retry * 1000);
fn();
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);
一个库可以很容易地做到这一点:promise-retry。
以下是一些测试它的示例:
const promiseRetry = require('promise-retry');
期望第二次尝试成功:
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);
});
});
预计只有一次重试:
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();
});
});
我的 TypeScript 解决方案:
export const wait = (milliseconds: number): Promise<void> =>
new Promise(resolve => {
setTimeout(() => resolve(), milliseconds);
});
export const retryWithDelay = async (
fn,
retries = 3,
interval = 300
): Promise<void> =>
fn().catch(async error => {
if (retries <= 0) {
return Promise.reject(error);
}
await wait(interval);
return retryWithDelay(fn, retries - 1, interval);
});
根据上面的解决方案,修复了等待毫秒,因为它默认为 50 秒而不是毫秒,现在抛出导致失败的错误而不是硬编码的测量。
我给你一个异步/等待的解决方案,玩得开心:)
async function scope() {
/* Performs an operation repeatedly at a given frequency until
it succeeds or a timeout is reached and returns its results. */
async function tryUntil(op, freq, tout) {
let timeLeft = tout;
while (timeLeft > 0) {
try {
return op();
} catch (e) {
console.log(timeLeft + " milliseconds left");
timeLeft -= freq;
}
await new Promise((resolve) => setTimeout(() => resolve(), freq));
}
throw new Error("failed to perform operation");
}
function triesToGiveBig() {
const num = Math.random();
if (num > 0.95) return num;
throw new Error();
}
try {
console.log(await tryUntil(triesToGiveBig, 100, 1000));
} catch (e) {
console.log("too small :(");
}
}
scope();
以防万一有人正在寻找更通用的解决方案。这是我的两分钱:
辅助功能:
/**
* Allows to repeatedly call
* an async code block
*
* @callback callback
* @callback [filterError] Allows to differentiate beween different type of errors
* @param {number} [maxRetries=Infinity]
*/
function asyncRetry(
callback,
{ filterError = (error) => true, maxRetries = Infinity } = {}
) {
// Initialize a new counter:
let tryCount = 0;
// Next return an async IIFY that is able to
// call itself recursively:
return (async function retry() {
// Increment out tryCount by one:
tryCount++;
try {
// Try to execute our callback:
return await callback();
} catch (error) {
// If our callback throws any error lets check it:
if (filterError(error) && tryCount <= maxRetries) {
// Recursively call this IIFY to repeat
return retry();
}
// Otherwise rethrow the error:
throw error;
}
})();
}
演示
尝试 2 次:
await asyncRetry(async () => {
// Put your async code here
}, { maxRetries = 2 })
尝试 2 次,仅在 DOMError
秒内重试:
await asyncRetry(async () => {
// Put your async code here
}, {
maxRetries = 2,
filterError: (error) => error instance of DOMError
})
精细重试:(不要这样做!
await asyncRetry(async () => {
// Put your async code here
})
简单承诺重试:
function keepTrying(otherArgs, promise) {
promise = promise||new Promise();
// try doing the important thing
if(success) {
promise.resolve(result);
} else {
setTimeout(function() {
keepTrying(otherArgs, promise);
}, retryInterval);
}
}
这对我来说非常有效:
async wait(timeInMilliseconds: number, name?: string) {
const messageSuffix = name ? ` => ${name}` : ""
await this.logger.info(`Waiting for ${timeInMilliseconds} ms${messageSuffix}`).then(log => log())
return new Promise<void>(resolve => setTimeout(resolve, timeInMilliseconds))
}
async waitUntilCondition(name: string, condition: () => boolean | Promise<boolean>, scanTimeInSeconds: number, timeoutInSeconds: number) {
await this.logger.info(`Waiting until condition: name=${name}, scanTime: ${scanTimeInSeconds} s, timeout: ${timeoutInSeconds} s`).then(log => log())
const timeoutInMillis = timeoutInSeconds * 1000
return new Promise<void>(async (resolve, reject) => {
const startTime = new Date().getTime()
let completed = false
let iteration = 0
while (!completed) {
if (iteration++ > 0) {
const timingOutInSeconds = Math.round((timeoutInMillis - (new Date().getTime() - startTime)) / 1000.0)
await this.wait(scanTimeInSeconds * 1000, `${name}, timing out in ${timingOutInSeconds} s`)
}
try {
completed = await condition()
if (completed) {
resolve()
return
}
} catch (error: any) {
reject(error)
throw error
}
const waitTimeMillis = new Date().getTime() - startTime
if (waitTimeMillis > timeoutInMillis) {
reject(`The condition '${name}' timed out. Time waited: ${waitTimeMillis / 1000} seconds`)
return
}
}
})
}
一种控制操作是否确实可以重试的方法
在实践中,我发现我们不应该假设操作总是可重试的,泛型retry
帮助程序需要一种方法将该检查委托给更高级别的调用代码。下面的示例显示了对我有用的方法。
/* The retry function takes a function to invoke, and a set
* of optional parameters to control the delay between retries
* (no backoff algorithm implemented here, but other example
* show how you might add that one), how many times to attempt
* retrying and also a way to check if a retry should be
* attempted.
*
* And it returns a Promise that can be used in promise-
* chaining and other async patterns.
*
*/
const retry = (fn,
ms = 1000,
maxRetries = 2,
fnRetryable) => new Promise((resolve, reject) => {
var retries = 0;
if(!fnRetryable) {
// default to always retryable
fnRetryable = function() { return true };
}
fn()
.then(resolve)
.catch((err) => {
if(!fnRetryable(err)) {
return reject('Non-retryable');
} else {
setTimeout(() => {
++retries;
if(retries == maxRetries) {
return reject('Max retries exceeded');
}
retry(fn, ms).then(resolve);
}, ms);
}
})
});
function doFoo(opts) {
// Return a Promise that resolves after doing something with opts
// or rejects with err.statusCode
}
function doFooWithRetry(opts, ms = 1000, maxRetries = 2) {
var attempt = function() {
return doFoo(opts);
}
var retryable = function(err) {
// example, retry on rate limit error
if(err && err.statusCode == 429) {
return true;
} else {
return false;
}
}
return retry(attempt, ms, maxRetries, retryable);
}
唯一易于使用的纯 JavaScript 零依赖异步重试库。
例
const { retry } = require('@ajimae/retry')
function exec() {
// This will be any async or sync action that needs to be retried.
return new Promise(resolve => {
setTimeout(() => {
resolve({ message: 'some async data' })
}, 1500)
})
}
// takes the response from the exec function and check if the condition/conditions are met
function predicate(response, retryCount) {
console.log(retryCount) // goes from 0 to maxRetries
// once this condition is met the retry exits
return (response.message == 'some async data')
}
(async function main() {
// enable or disable an exponential backoff behaviour if needed.
const result = await retry(exec, predicate, { maxRetries: 5, backoff: true })
console.log(result) // { message: 'some async data' }
})()
PS:我创作了这个库。
- JavaScript:在源404上重试
- 用于操纵DOM API的Javascript设计模式
- 用于多个选项卡和模块化的knockoutjs设计模式
- 用分隔符分隔具有多个整数值的字符串的Javascript"重试错误的值
- Ajax在NodeJS中为一个耗时的请求请求多次重试
- 自动化设计模式c++
- jQuery自动完成标记新的标签设计模式
- 有没有一种方法可以在设计模式下将ng模型或工厂绑定到iframe
- 如何仅在RxJs中可观察到的源发出的特定错误上重试
- 使用错误的 ng 模式重置 angularjs 表单字段
- Javascript MVVM 设计模式 - 如何跟踪脏状态以及谁应该做 Ajaxing
- 包含 2 个或更多对象的页面上的 JavaScript 设计模式
- 在 Node 中.js释放 zalgo 的设计模式为什么异步路径是一致的
- BreezeJS中央错误处理程序和自动重试
- Django设计模式-在加载时填充客户端JavaScript变量的方法
- 如何捕获请求中的错误,然后打开模态,然后在模态使用RxJS关闭时重试
- Angular2 http重试逻辑
- 用于处理成功、失败、重试的异步请求的设计模式?(JavaScript)
- 重试模式弹出AngularJS
- 承诺重试设计模式