NodeJS 如何重试Promise解析N次,尝试之间有延迟?

mitkmikd  于 2023-05-06  发布在  Node.js
关注(0)|答案(5)|浏览(118)

我想让一些JavaScript代码接受3个参数:

  • 返回Promise的函数。
  • 最大尝试次数。
  • 每次尝试之间的延迟。

最后我使用了一个for循环。我不想使用递归函数:这样,即使有50次尝试,调用堆栈也不会增加50行。
下面是代码的typescript版本:

/**
 * @async
 * @function tryNTimes<T> Tries to resolve a {@link Promise<T>} N times, with a delay between each attempt.
 * @param {Object} options Options for the attempts.
 * @param {() => Promise<T>} options.toTry The {@link Promise<T>} to try to resolve.
 * @param {number} [options.times=5] The maximum number of attempts (must be greater than 0).
 * @param {number} [options.interval=1] The interval of time between each attempt in seconds.
 * @returns {Promise<T>} The resolution of the {@link Promise<T>}.
 */
export async function tryNTimes<T>(
    {
        toTry,
        times = 5,
        interval = 1,
    }:
        {
            toTry: () => Promise<T>,
            times?: number,
            interval?: number,
        }
): Promise<T> {
    if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
    let attemptCount: number;
    for (attemptCount = 1; attemptCount <= times; attemptCount++) {
        let error: boolean = false;
        const result = await toTry().catch((reason) => {
            error = true;
            return reason;
        });

        if (error) {
            if (attemptCount < times) await delay(interval);
            else return Promise.reject(result);
        }
        else return result;
    }
}

上面使用的delay函数是一个承诺的超时:

/**
 * @function delay Delays the execution of an action.
 * @param {number} time The time to wait in seconds.
 * @returns {Promise<void>}
 */
export function delay(time: number): Promise<void> {
    return new Promise<void>((resolve) => setTimeout(resolve, time * 1000));
}

为了澄清:上面的代码工作,我只是想知道这是否是一个“好”的方法,如果不是,我如何改进它。
有什么建议吗?先谢谢你的帮助。

kiz8lqtg

kiz8lqtg1#

我不想使用递归函数:这样,即使有50次尝试,调用堆栈也不会增加50行。
这不是个好借口调用堆栈不会因异步调用而溢出,当递归解决方案比迭代解决方案更直观时,您可能应该使用它。
最后我使用了一个for循环。这是一个“好”的方法吗?如果不是,我该如何改进它?
for循环很好。它从1开始有点奇怪,但基于0的循环更习惯。
然而,不好的是你奇怪的错误处理。这个布尔型error标志在代码中不应该有位置。Using .catch() is fine,但是try/catch也可以工作,应该是首选。

export async function tryNTimes<T>({ toTry, times = 5, interval = 1}) {
    if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
    let attemptCount = 0
    while (true) {
        try {
            const result = await toTry();
            return result;
        } catch(error) {
            if (++attemptCount >= times) throw error;
        }
        await delay(interval)
    }
}
r7xajy2e

r7xajy2e2#

您可能想看看async-retry,它正是您所需要的。这个包允许你重试异步操作,你可以配置(除其他事项外)重试之间的超时(即使有增加的因素),最大重试次数,…
通过这种方式,您不必重新发明轮子,而是可以依赖于在社区中广泛使用的经过验证的包。

wmtdaxz3

wmtdaxz33#

在Promise中使用递归函数不会对调用堆栈造成问题,因为Promise会立即返回,并且thencatch函数将在异步事件后调用。
一个简单的JavaScript函数应该是这样的:

function wait (ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

function retry (fn, maxAttempts = 1, delay = 0, attempts = 0) {
  return Promise.resolve()
    .then(fn)
    .catch(err => {
      if (attempts < maxAttempts) {
        return retry (fn, maxAttempts, delay, attempts + 1)
      }
      throw err
    })
}
nukf8bse

nukf8bse4#

你考虑过RxJS吗?

它非常适合在异步工作流中实现这种逻辑。
下面是一个例子,说明如何在不破坏公共API的情况下做到这一点。从Promise转换为Observable并返回)。在实践中,你可能希望在任何给定的项目中使用RxJSPromises,而不是混合使用它们。

/**
 * @async
 * @function tryNTimes<T> Tries to resolve a {@link Promise<T>} N times, with a delay between each attempt.
 * @param {Object} options Options for the attempts.
 * @param {() => Promise<T>} options.toTry The {@link Promise<T>} to try to resolve.
 * @param {number} [options.times=5] The maximum number of attempts (must be greater than 0).
 * @param {number} [options.interval=1] The interval of time between each attempt in seconds.
 * @returns {Promise<T>} The resolution of the {@link Promise<T>}.
 */
export async function tryNTimes<T>(
    {
        toTry,
        times = 5,
        interval = 1,
    }:
        {
            toTry: () => Promise<T>,
            times?: number,
            interval?: number,
        }
): Promise<T> {
    if (times < 1) throw new Error(`Bad argument: 'times' must be greater than 0, but ${times} was received.`);
    let attemptCount: number;

    return from(toTry)
        .pipe(
            retryWhen(errors =>
                errors.pipe(
                    delay(interval * 1000),
                    take(times - 1)
                )
            )
        )
        .toPromise();
}

可能不值得为这一个逻辑添加整个库,但如果您的项目涉及许多复杂的异步工作流,那么RxJS非常棒。

qmelpv7a

qmelpv7a5#

下面是一些可以工作的代码。

助手

interface RetryProps {
  attempts?: number
  delay: number
  fn: () => boolean
  maxAttempts: number
}

function retry({ fn, maxAttempts = 1, delay = 1000, attempts = 5 }: RetryProps) {
  return new Promise((resolve, reject) => {
    if (fn()) resolve(true)
    else {
      if (attempts < maxAttempts) {
        setTimeout(
          () =>
            retry({ fn, maxAttempts, delay, attempts: attempts + 1 })
              .then(() => resolve(true))
              .catch((err) => reject(err)),
          delay
        )
      } else reject('Could not resolve function.')
    }
  })
}

然后向它传递一个函数,成功时返回true

使用示例

retry({
  fn: function () {
    // Whatever you want to test, return true upon success.
    const elExists = !document.getElementById('myRandomELement')
    console.log('Element present in DOM?', elExists)
    return elExists
  },
  maxAttempts: 4,
  delay: 2000,
})
  .then(() => console.log('Done'))
  .catch(() => console.log("Didn't pan out"))

因为它返回一个promise,你可以await它或使用then/catch来解决它。

相关问题