当网络请求发生失败时,假如采用自动重新发送请求的策略,会面临两个问题,1:重试的次数,2:请求发送的间隔。根据不同的应用场景、业务环境,重试的次数和请求间隔是不一致的。
对于如何确定请求的间隔。间隔过长,软件的体验感变差,甚至是在重新建立连接之前就被用户中断;间隔过短,容易造成网络阻塞。一种规避(平衡)方法是在间隔过长和间隔过短之间做平衡,即动态让间隔时间增长。要实现间隔动态增长,需要两个参数,基本的间隔(首次重试的时间间隔)和间隔时间增长的系数(不一定是线性的)。
需要重试请求的场景有两种,一是网络请求出错,二是一次请求中部分数据写入\读取部分失败,需要对失败的部分进行重新建立连接。
请求出错的情况
- 整体逻辑:
const callRetry = async (callFunc, depth=0) => {
try {
return await callFunc(); // 重试次数满足要求时,发送请求
} catch(error) {
if(depth>7){
throw error;
}
await wait(2**depth*100) // 异步等待时间
console.log(`retry:${depth}次`)
return callRetry(callFunc, depth+1) // 递归-重试
}
}
- 异步延时:
const wait= (ms) => new Promise((res => {setTimeout(res, ms)}));
- 模拟错误的网络请求:
const callFunc = (sucessProbability=0.5) => {
return new Promise((res, rej)=> Math.random < sucessProbability?res("success"):rej("error"))
}
- 使用:
callRetry(callFunc,1)
.then(res=>{
console.log(res)
})
.catch(error => {
console.log(error)
})
请求部分失败的情况
- 整体逻辑:
const callWithProgress = async (fn, status, depth = 0) => {
const result = await fn(status); // TODO 万一这里请求失败怎么办
// 检查是否完成请求
if (result.progress === 1) {
// 请求完成
return result.result;
}else {
// 请求未完成
if (depth > 7) {
throw result;
}
await wait(2 ** depth * 10);
return callWithProgress(fn, result.progress, depth + 1);
}
}
- 模拟请求过程:
const progressingOperation = async (startProgress = 0) => {
await wait(10);
const progress = Math.round(Math.min(startProgress + Math.random() / 3, 1) * 10) / 10;
return {
progress,
result: progress === 1 ? "result" : undefined,
};
}
- 调用:
callWithProgress(progressingOperation).then(res=>console.log(res)).catch(error=>console.log(error))