【问题标题】:How to retry upon async/await function catching error?如何在异步/等待函数捕获错误时重试?
【发布时间】:2020-05-31 13:42:23
【问题描述】:

我对 JS 和一般编程还很陌生,在尝试在我的异步函数中实现 try/catch 时遇到了以下问题。它看起来像这样:

(async function main() {
    try {
        await myFunc();
    } catch (e) {
        console.log(e)
    }

    setTimeout(main, 2000);
}) ();

如您所见,它通过 setTimeout 调用对其自身进行循环,使其无限...除非它捕获错误。自然,我想尝试在 e 的某些实例中重试 myFunc,但不知道如何在不删除 setTimeout 的情况下实现它(这里 2 秒的延迟很重要)。

我尝试使用标签:

let critErrCount;

(async function main() {
    critErrCount = 0;
    lbl:
    try {
        await myFunc();
    } catch (e) {
        if (e instanceof errType1) {
            console.log(`errType1: ${e}`);
            continue lbl;
        } else if (e instanceof errType2) {
            critErrCount += 1;
            console.log(`errType2: ${e}`);
            console.log(`critErrCount: ${critErrCount}`);

            if (critErrCount == 5) {
                console.log(`Too many critErrs, aborting...`)
                process.exit;
            }
            continue lbl;
        } else { 
            process.exit
        }
    }

    setTimeout(main, 2000);
}) ();

当然,它没有用,因为据我所知,“继续”的行为不像其他语言中的“goto”,只能在适当的循环中工作(forEach、while、for 等)。由于由于 nodejs 运行时细节(显示为“非法继续语句”),catch 块内的 setTimeout 也不起作用,所以我没有想法。

【问题讨论】:

  • 让它无限...除非它捕获错误。即使有错误,递归setTimeout 仍然可以工作,对吧?
  • 你为什么不用setInterval
  • @CertainPerformance 这是正确的。但这里有一个问题:经过一些测试,我确定如果main 函数即使只捕获一次错误,它也会在几次(数量总是不同)迭代后由于未知原因自行终止。如果没有发现错误,它将继续按预期工作,直到发现一个错误
  • 我很确定给定的代码不会导致你所说的。您可以将实时 sn-p 编辑到显示超时最终失败的问题中吗?我会认为它会一直有效
  • @fadeys.work setInterval 不考虑函数执行时间,我的目标是等待2000代码执行后

标签: javascript loops async-await try-catch settimeout


【解决方案1】:

您可以使用重试机制,让您重试异步函数调用所需的次数。 In this blogpost,我写了如何做到这一点,但这里是实现:

/**
 * Execute a promise and retry with exponential backoff
 * based on the maximum retry attempts it can perform
 * @param {Promise} promise promise to be executed
 * @param {function} onRetry callback executed on every retry
 * @param {number} maxRetries The maximum number of retries to be attempted
 * @returns {Promise} The result of the given promise passed in
 */
function retry(promise, onRetry, maxRetries) {
  // Notice that we declare an inner function here
  // so we can encapsulate the retries and don't expose
  // it to the caller. This is also a recursive function
  async function retryWithBackoff(retries) {
    try {
      // Make sure we don't wait on the first attempt
      if (retries > 0) {
        // Here is where the magic happens.
        // on every retry, we exponentially increase the time to wait.
        // Here is how it looks for a `maxRetries` = 4
        // (2 ** 1) * 100 = 200 ms
        // (2 ** 2) * 100 = 400 ms
        // (2 ** 3) * 100 = 800 ms
        const timeToWait = 2 ** retries * 100;
        console.log(`waiting for ${timeToWait}ms...`);
        await waitFor(timeToWait);
      }
      return await promise();
    } catch (e) {
      // only retry if we didn't reach the limit
      // otherwise, let the caller handle the error
      if (retries < maxRetries) {
        onRetry();
        return retryWithBackoff(retries + 1);
      } else {
        console.warn('Max retries reached. Bubbling the error up')
        throw e;
      }
    }
  }

  return retryWithBackoff(0);
}

/**
 * Wait for the given milliseconds
 * @param {number} milliseconds The given time to wait
 * @returns {Promise} A fulfiled promise after the given time has passed
 */
function waitFor(milliseconds) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

下面是你如何使用retry 助手:

/** Fake an API Call that fails for the first 3 attempts
 * and resolves on its fourth attempt.
 */
function generateFailableAPICall() {
  let counter = 0;
  return function () {
    if (counter < 3) {
      counter++;
      return Promise.reject(new Error("Simulated error"));
    } else {
      return Promise.resolve({ status: "ok" });
    }
  };
}

/*** Testing our Retry with Exponential Backoff */
async function test() {
  const apiCall = generateFailableAPICall();
  const result = await retry(
    apiCall,
    () => {
      console.log("onRetry called...");
    },
    4
  );

  console.log("result: ", result);
}

test();

【讨论】:

  • 自从一年前我发布了这个问题以来,我以与您在此处提出的类似方式从头开始重写了我的脚本,因此您的回答非常合适。谢谢
猜你喜欢
  • 2018-04-20
  • 2014-12-01
  • 2017-11-09
  • 2019-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-06
相关资源
最近更新 更多