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

Posted

技术标签:

【中文标题】如何重试 Promise 解决 N 次,尝试之间有延迟?【英文标题】:How to retry a Promise resolution N times, with a delay between the attempts? 【发布时间】:2019-08-31 22:19:49 【问题描述】:

我想要一些 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));

澄清一下:上面的代码有效,我只是想知道这是否是一种“好”的做法,如果不是,我该如何改进它。

有什么建议吗?提前感谢您的帮助。

【问题讨论】:

由于代码可以正常工作并且应该没有问题,这可能是codereview.stackexchange.com的问题 这个我不知道,谢谢。 Promise Retry Design Patterns的可能重复 【参考方案1】:

你考虑过 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 就很棒。

【讨论】:

这不是有点像用大炮射麻雀吗?【参考方案2】:

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

【讨论】:

【参考方案3】:

您可能想看看async-retry,它正是您所需要的。这个包允许你重试异步操作,你可以配置(除其他外)重试之间的超时(即使是增加的因素)、最大重试次数……

这样您就不必重新发明***,而是可以依赖在社区中广泛使用的经过验证的软件包。

【讨论】:

【参考方案4】:

使用带有 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
    )

【讨论】:

以上是关于如何重试 Promise 解决 N 次,尝试之间有延迟?的主要内容,如果未能解决你的问题,请参考以下文章

死锁后,为啥应用程序代码要等待再重试?

mybatis往数据库中插入时如何判断失败尝试重试5次

Filter+Redis解决项目之间调用的幂等性

如何做出递归承诺

邮递员/纽曼在失败的情况下重试

Angular 7 - 如何在某些响应状态代码上重试 http 请求?