几种重复请求数据的问题及解决方法
最近封控在家,回顾问题解解闷。今天来讲讲重复请求的问题。
问题汇总
- 表单提交按钮重复点击
- 查询按钮重复点击
- 可能多个因素同时触发某个请求
- 页面自动刷新数据(如:数据大屏)
- 同一页面内有多个tab用同一个table,tab1下的数据量大,tab2下的数据量极小,tab1数据未获取到的时候切换到tab2,可能渲染的是tab1的数据
解决的是什么问题
解决重复请求的问题本质上是解决这三类问题:
- 用户体验
- 页面性能
- 数据正确且安全,关于这个问题,前端是无法完全杜绝的,必须后端进行兜底,这里不是今天的重点。
分类分析
提交按钮重复点击
这是最常见的问题,重复提交会造成多条数据入库。点击提交给个loading提示过渡,期间按钮不可再次触发就可以。
查询按钮重复点击
如果查询按钮点一下就设置loading,体验其实并不好,但是一直请求,数据不断重新渲染,又会影响性能。 我第一反应是用防抖,如果误触只请求最后一次就好了。
// 防抖
// fn: 回调函数
// delay: 延时时间
export const debounce = (fn, delay = 1000) => {
let timer = null;
return function() {
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
}, 1000);
};
};
直到我疯狂点击搜索引擎的搜索按钮,我发现人家用的不是防抖,我陷入沉思,如果我的用户像我一样,那防抖就会让他无法快速看到数据,所以我又觉得应该用节流。
// 节流
export const throttle = (fn, delay = 1000) => {
let old = 0;
return function() {
let now = new Date().valueOf();
if (now - old > delay) {
fn.apply(this, arguments);
old = now;
}
};
};
axios取消重复请求
当多个因素瞬时一起触发某个请求,你会希望只处理最后一次请求,那么可以从axios入手,cancel掉多余的请求
// axios.js
……
const CANCEL_RETRANSMIT = []; // 这里存储需要处理重复请求的url
let s = 1; // 单位秒
let pending = []; // 声明一个数组用于存储每个请求的取消函数和axios标识
let cancelToken = Axios.CancelToken;
let removePending = config => {
pending = pending.filter(i => new Date().getTime() - i.t < s * 1000); // 保留s秒内发的请求
if (
pending.length > 1 &&
pending[pending.length - 2].u === config.url &&
pending[pending.length - 1].t - pending[pending.length - 2].t < s * 1000
) {
pending[pending.length - 2].f(); //执行取消操作
pending.splice(pending.length - 2, 1);
}
};
……
// 请求拦截器内
axios.interceptors.request.use(
async config => {
……
const url = config.url || '';
// 删除重复请求
if (CANCEL_RETRANSMIT.includes(config.url)) {
config.cancelToken = new cancelToken(c => {
// 用请求地址&请求参数拼接的字符串
pending.push({
u: url,
t: new Date().getTime(),
f: c,
});
});
await removePending(config);
}
return config;
},
……
);
这种方式只是让前端减少处理重复请求,但是后端依然会收到多个请求。
页面自动刷新数据
有些页面需要定时自动重复请求数据更新页面,比如:数据大屏、带状态信息的数据表格。 大多数前端仔看到定时任务第一反应就是setInterval。
setInterval(function(){
//要执行的代码
},200);
我们先复习一下setInterval的特点:
- 会阻塞
- 无论页面显示隐藏,都会执行
- 退出需要清除定时器
- 间隔时间可以任意定义
- 浏览器兼容性极好
那么有没有一种东西能解决setInterval的缺点?这个就是requestAnimationFrame,简称rAF。 rAF的特点:
- 按浏览器帧执行,低阻塞。如果是动画则流畅度会更高。
- 仅当页面显示时会执行,页面在后台不执行,这样可以减小CPU占用
- 退出需要清除定时器
- 间隔时间是每一帧的间隔时间,可以封装方法任意定义
- 浏览器兼容比setInterval差点 用rAF封装出来的定时器如下
export const rafInterval = (fn, delay = 3000) => {
let start = 0;
function fun() {
const timestamp = new Date().valueOf();
if (start === undefined) start = timestamp;
if (timestamp - start > delay) {
start = timestamp;
fn.apply(this, arguments);
}
requestAnimationFrame(fun);
}
return function() {
requestAnimationFrame(fun);
};
};
前面提过requestAnimationFrame兼容性不够好,那么就加个判断
var requestAnimFrame = (function() {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
}
);
})();
export const rafInterval = (fn, delay = 3000) => {
……
};
同页面根据条件展示不同表格数据
实际场景:同一页面内有多个tab用同一个table组件,tab1下的数据量大,tab2下的数据量极小,tab1数据未获取到的时候切换到tab2,可能tab1的数据比tab2还晚返回,table渲染的就会是tab1的数据
解决思路:
- 如果是同一个url发出的请求,用axios的cancel方法,把存在的pending状态的请求cancel
- 如果是不同url发出的请求,传个标记给后端,后端返回数据带给前端
转载自:https://juejin.cn/post/7159932683651383332