包装任何经常失败的 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:

TS Playground

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