包装任何经常失败的 await 函数,以便它自己重试
Wrap any await function that fails often so that it retries itself
我的代码库往往会调用不可靠的服务器。
正因为如此,我养成了将代码段包装在以下位置的习惯:
while(true) {
try {
let returnVal = await functionCall(param);
break;
} catch (e) {
console.log(e);
await delay(1000);
}
}
但是随着我添加更多功能,例如错误格式设置和超时,这变得非常冗长和笨拙。
我能否以某种方式(使用任何 return 值)包装一个通用函数,以便我可以调用
let returnVal = retryAwait(functionCall(param));
我试过创建函数并通过闭包传递参数:
async function retry(fn: Function) {
while(true) {
try {
return await fn();
} catch (e) {
console.log(e);
await delay(1000);
}
}
}
let returnVal = await retry(function(){return functionCall(param)});
这似乎可行,但我想确保我没有搬起石头砸自己的脚?当我开始处理 Promise return 一个不正确的值时,我会遇到问题吗?
这是解决我的问题的正确方法吗?
I presume that you are working on solving this problem for learning purposes, as many promise "retry" functions already exist in community modules/packages.
显然,您希望在传递给 retry
函数(使用 try/catch
)的回调函数中处理有问题的响应,并尝试在重新抛出之前恢复(如果可能)和延迟后重新启动重试循环。但是,一旦陷入僵局,就会抛出异常(可能会从响应中获取一些有用的数据等)。
您可以修改您的函数以接受在抛出异常的情况下调用的第二个参数(另一个函数):
第二个函数计算下一次重试前的延迟超时间隔。它可以使用上下文信息:抛出的异常、当前尝试次数(计数)以及计算下一个超时值的总耗时(例如 exponential backoff)。如果 return 间隔小于 0
,重试函数将停止并且 returns undefined
:
function delay (timeoutMs: number): Promise<void> {
return new Promise(res => setTimeout(res, timeoutMs));
}
type Fn<
Params extends readonly unknown[] = any[],
Result = any,
> = (...params: Params) => Result;
type RetryExceptionContext = {
/** The current number of tries */
count: number;
/** The total number of milliseconds since before the first try */
elapsed: number;
/** The exception which was thrown from the `try` block */
exception: unknown;
};
/**
* A function which computes the next delay interval.
*
* - If a negative number is returned, the retry function is stopped,
* returning a value of `undefined`.
*
* - If a positive number is returned, the value is used as the number of milliseconds
* to wait until the next retry.
*/
type RetryExceptionHandler = Fn<[context: RetryExceptionContext], number | Promise<number>>;
async function retry <F extends Fn>(
fn: F,
getNextTimeout: RetryExceptionHandler,
): Promise<ReturnType<F> | undefined> {
let count = 0;
const start = performance.now();
while(true) {
count += 1;
try {
return await fn();
}
catch (exception) {
const elapsed = performance.now() - start;
const timeout = await getNextTimeout({count, elapsed, exception});
if (timeout < 0) return undefined;
await delay(timeout);
}
}
}
async function example () {
async function unpredictable (): Promise<string> {
if (Math.random() < 0.5) return 'Success';
throw new Error('Fail');
}
const result = await retry(unpredictable, (context) => {
if (context.count > 3) return -1;
return 1000;
});
if (typeof result !== 'undefined') {
result // string
}
else {
result // undefined
// you decided to stop it
}
}
编辑:这是上面的一个变体,带有一个额外的 state
参数,用于任意数据,可以在两个回调函数中使用:TS Playground
我的代码库往往会调用不可靠的服务器。
正因为如此,我养成了将代码段包装在以下位置的习惯:
while(true) {
try {
let returnVal = await functionCall(param);
break;
} catch (e) {
console.log(e);
await delay(1000);
}
}
但是随着我添加更多功能,例如错误格式设置和超时,这变得非常冗长和笨拙。
我能否以某种方式(使用任何 return 值)包装一个通用函数,以便我可以调用
let returnVal = retryAwait(functionCall(param));
我试过创建函数并通过闭包传递参数:
async function retry(fn: Function) {
while(true) {
try {
return await fn();
} catch (e) {
console.log(e);
await delay(1000);
}
}
}
let returnVal = await retry(function(){return functionCall(param)});
这似乎可行,但我想确保我没有搬起石头砸自己的脚?当我开始处理 Promise return 一个不正确的值时,我会遇到问题吗?
这是解决我的问题的正确方法吗?
I presume that you are working on solving this problem for learning purposes, as many promise "retry" functions already exist in community modules/packages.
显然,您希望在传递给 retry
函数(使用 try/catch
)的回调函数中处理有问题的响应,并尝试在重新抛出之前恢复(如果可能)和延迟后重新启动重试循环。但是,一旦陷入僵局,就会抛出异常(可能会从响应中获取一些有用的数据等)。
您可以修改您的函数以接受在抛出异常的情况下调用的第二个参数(另一个函数):
第二个函数计算下一次重试前的延迟超时间隔。它可以使用上下文信息:抛出的异常、当前尝试次数(计数)以及计算下一个超时值的总耗时(例如 exponential backoff)。如果 return 间隔小于 0
,重试函数将停止并且 returns undefined
:
function delay (timeoutMs: number): Promise<void> {
return new Promise(res => setTimeout(res, timeoutMs));
}
type Fn<
Params extends readonly unknown[] = any[],
Result = any,
> = (...params: Params) => Result;
type RetryExceptionContext = {
/** The current number of tries */
count: number;
/** The total number of milliseconds since before the first try */
elapsed: number;
/** The exception which was thrown from the `try` block */
exception: unknown;
};
/**
* A function which computes the next delay interval.
*
* - If a negative number is returned, the retry function is stopped,
* returning a value of `undefined`.
*
* - If a positive number is returned, the value is used as the number of milliseconds
* to wait until the next retry.
*/
type RetryExceptionHandler = Fn<[context: RetryExceptionContext], number | Promise<number>>;
async function retry <F extends Fn>(
fn: F,
getNextTimeout: RetryExceptionHandler,
): Promise<ReturnType<F> | undefined> {
let count = 0;
const start = performance.now();
while(true) {
count += 1;
try {
return await fn();
}
catch (exception) {
const elapsed = performance.now() - start;
const timeout = await getNextTimeout({count, elapsed, exception});
if (timeout < 0) return undefined;
await delay(timeout);
}
}
}
async function example () {
async function unpredictable (): Promise<string> {
if (Math.random() < 0.5) return 'Success';
throw new Error('Fail');
}
const result = await retry(unpredictable, (context) => {
if (context.count > 3) return -1;
return 1000;
});
if (typeof result !== 'undefined') {
result // string
}
else {
result // undefined
// you decided to stop it
}
}
编辑:这是上面的一个变体,带有一个额外的 state
参数,用于任意数据,可以在两个回调函数中使用:TS Playground