如何实现请求失败后的指数退避重试并监控重试次数?
在开发支付接口时遇到请求失败自动重试的问题。现在用递归实现指数退避重试,但发现重试次数没有正确记录到监控系统,导致无法统计真实失败率。
尝试过给每个Promise链添加.then(() => reportRetryCount()),但多次重试时count值会跳变。比如第一次失败重试了3次,监控显示count是3,但第二次同样的情况却显示count变成了6?
代码示例:
let retryCount = 0;
function payRequest() {
return fetch('/api/pay')
.then(response => {
if (!response.ok) throw new Error('失败');
reportRetryCount(retryCount); // 这里总是记录错误值
return response.json();
})
.catch(err => {
retryCount++;
if (retryCount > 3) throw err;
return new Promise(resolve =>
setTimeout(resolve, Math.pow(2, retryCount)*1000)
).then(payRequest);
});
}
现在每次重试都会递增retryCount,但多次调用payRequest时,count似乎在不同请求间共享了?比如用户连续两次支付失败,第二次的count居然从上次的3开始继续计数…
retryCount被定义在全局作用域,导致它在多次调用payRequest时被共享了。这种情况下,不同请求之间的重试计数会互相干扰,导致监控系统记录的值不准确。通用的做法是把
retryCount封装到函数内部,确保每个请求都有自己独立的计数器。另外,为了避免递归带来的复杂性,可以用循环结合 Promise 实现更清晰的指数退避逻辑。下面是改进后的代码:这里的关键点有三个:第一,把
retryCount放到函数作用域内,确保每次调用payRequest都是一个全新的计数器;第二,在每次成功或最终失败时调用reportRetryCount,确保上报的是正确的值;第三,使用递归函数attempt来实现指数退避,逻辑更清晰。如果觉得递归不够直观,也可以改成基于循环的实现方式:
这种写法用
async/await和循环替代了递归,代码逻辑更直观,尤其适合不喜欢递归的开发者。总结一下,核心问题是
retryCount的作用域不对,导致多个请求共享了同一个计数器。解决方法就是把计数器封装到函数内部,同时确保每次成功或失败时正确上报重试次数。这两种实现方式都可以解决问题,选你觉得更顺手的就行。retryCount被定义成了全局变量,导致多个请求之间共享了同一个计数器。JS里面这种问题很常见,尤其是在异步操作中,变量作用域一不小心就会踩坑。解决办法是把
retryCount的状态封装到函数内部,每次调用payRequest时都重新初始化计数器,确保每个请求的重试次数是独立的。可以用一个闭包来实现:这样每次调用
createPayRequest都会生成一个新的payRequest函数,它的retryCount是独立的,不会被其他请求干扰。至于监控重试次数,
reportRetryCount应该在请求成功或者最终失败时调用,而不是在每次重试时都调用,否则会导致数据不准确。你可以稍微调整一下逻辑:总结一下:把
retryCount封装到闭包里,确保每个请求独立计数;在合适的地方调用reportRetryCount,避免重复上报。这样就能正确统计重试次数了。