前言
精心写好的 AJAX 请求,本地测试百发百中,一到线上就间歇性 “摆烂”—— 网络抽风、服务器手抖、甚至只是宇宙射线干扰(bushi),请求就哐哐报错。
要是每次失败都直接抛给用户 “请求失败,请刷新页面”,用户体验直接原地爆炸。这时候,给请求加个 “重试机制” 就显得尤为重要了 —— 就像打游戏复活,失败了咱再来一次,让它失败了能自动重来。
一、先看痛点:不稳定的 AJAX 请求
先看这个模拟 AJAX 请求的代码,它模拟了网络请求的随机性:有 80% 的概率失败,20% 的概率成功,就像现实中那些不靠谱的接口一样:
// 模拟不稳定的AJAX请求
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 生成0-9的随机数
const random = ~~(Math.random() * 10);
if (random < 8) {
console.log('请求失败😭');
reject('fail');
} else {
console.log('请求成功🎉');
resolve('success');
}
}, 1000)
})
}
// 普通调用:失败了就凉了
ajax()
.then(res => console.log(res))
.catch(err => console.log(err))
直接调用这个ajax函数,大概率会看到 “请求失败” 的提示,要是这是用户支付的关键请求,不得把用户急哭?这时候,咱们的 “重试神器” 就该登场了。
二、打造重试函数:失败了咱再来!
核心思路很简单:写一个retry函数,接收 “要执行的请求函数” 和 “重试次数”,每次请求失败后,就减少一次重试次数,只要次数没耗光,就重新发起请求;直到成功,或者次数用完彻底失败。
// 模拟不稳定的AJAX请求
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const random = ~~(Math.random() * 10);
if (random < 8) {
console.log('请求失败😭');
reject('fail');
} else {
console.log('请求成功🎉');
resolve('success');
}
}, 1000)
})
}
// 重试核心函数:给请求加“复活甲”
function retry(fn, count) {
// 返回一个新的Promise,统一对外暴露结果
return new Promise((resolve, reject) => {
// 封装请求执行逻辑,方便递归调用
const run = () => {
fn()
.then((res) => {
// 请求成功:直接resolve结果,结束流程
resolve(res);
console.log(`终于成了!结果:${res}`);
})
.catch((err) => {
// 请求失败:先减少重试次数
count--;
console.log(`还剩${count}次重试机会`);
// 还有重试次数:递归调用run,再来一次
if (count) {
console.log('准备重试...');
run();
} else {
// 次数用完:彻底失败,reject结果
reject('重试次数耗尽,请求彻底失败💥');
}
})
}
// 首次执行请求
run();
})
}
// 调用重试函数:最多重试 3次
retry(ajax, 3)
.then(res => console.log(`最终结果:${res}`))
.catch(err => console.log(`最终结果:${err}`));
就算前两次都失败,第三次成功了,整个流程就会返回成功;如果 3 次都失败,就会输出:
关键逻辑拆解:
- 外层 Promise:
retry函数返回新 Promise,不管内部重试多少次,对外只暴露最终的成功 / 失败结果,符合 Promise 的链式调用习惯 - run 函数递归:把请求逻辑封装在
run里,失败后只要还有重试次数,就递归调用run,实现 “自动重试” - 次数控制:每次失败
count--,当count为 0 时,不再重试,直接reject,避免无限循环
三、进阶小技巧:让重试更 “聪明”
当然,实际开发中咱们的重试机制可以更完善,比如:
- 添加延迟重试:失败后不要立刻重试,等几百毫秒再试(避免高频请求压垮服务器)
- 指数退避:重试间隔越来越长(比如 1s→2s→4s),更符合网络请求的最佳实践
- 自定义失败条件:不是所有失败都重试(比如 404 是资源不存在,重试也没用)
举个加延迟的小改造:
function retry(fn, count, delay = 1000) {
return new Promise((resolve, reject) => {
const run = () => {
fn()
.then(res => {
resolve(res);
console.log(`终于成了!结果:${res}`);
})
.catch(err => {
count--;
console.log(`还剩${count}次重试机会`);
if (count) {
console.log(`等待${delay}ms后重试...`);
// 延迟重试
setTimeout(run, delay);
} else {
reject('重试次数耗尽,请求彻底失败💥');
}
})
}
run();
})
}
结语
写代码就像过日子,哪能事事一帆风顺? 修复 bug 也是人生中繁琐的乐趣。