如何重试Promise解决方案N次,两次尝试之间有延迟?

时间:2019-04-10 12:42:35

标签: javascript node.js typescript promise

我希望一些JavaScript代码将3件事作为参数:

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

我最终要做的是使用 ['123','456'] 循环。我不想使用递归函数:这样,即使尝试了50次,调用堆栈也不会再长50行。

这是该代码的打字稿版本:

for

上面使用的/** * @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

为了澄清:上面的代码有效,我只是想知道这是否是一种“好的”方式,如果不能,那么我将如何对其进行改进。

有什么建议吗?预先感谢您的帮助。

4 个答案:

答案 0 :(得分:1)

  

我不想使用递归函数:这样,即使尝试了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)
    }
}

答案 1 :(得分:1)

您可能想看看async-retry,它确实满足您的需求。此程序包使您可以重试异步操作,还可以配置重试之间的超时(包括增加的因素),重试的最大次数,等等。

通过这种方式,您不必重新发明轮子,而是可以依靠在社区中广泛使用的经过验证的软件包。

答案 2 :(得分:1)

在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
    })
}

答案 3 :(得分:0)

您考虑过RxJS吗?

在异步工作流程中实现这种逻辑非常好。

下面是一个如何在不破坏公共api的情况下执行此操作的示例(即,从Promise转换为Observable并返回)。实际上,您可能希望在任何给定项目中使用RxJS Promises,而不是混合使用它们。

/**
 * @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就是很好的选择。