如何在网络请求发生错误时自动重新发送请求

824 阅读2分钟

当网络请求发生失败时,假如采用自动重新发送请求的策略,会面临两个问题,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))