首页 > 解决方案 > 如何在异步/等待函数捕获错误时重试?

问题描述

我对 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 也不起作用,所以我没有想法。

标签: javascriptloopsasync-awaittry-catchsettimeout

解决方案


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

/**
 * 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();

推荐阅读