几种重复请求数据的问题及解决方法

lxf2023-03-11 12:01:02

最近封控在家,回顾问题解解闷。今天来讲讲重复请求的问题。

问题汇总

  1. 表单提交按钮重复点击
  2. 查询按钮重复点击
  3. 可能多个因素同时触发某个请求
  4. 页面自动刷新数据(如:数据大屏)
  5. 同一页面内有多个tab用同一个table,tab1下的数据量大,tab2下的数据量极小,tab1数据未获取到的时候切换到tab2,可能渲染的是tab1的数据

解决的是什么问题

解决重复请求的问题本质上是解决这三类问题:

  1. 用户体验
  2. 页面性能
  3. 数据正确且安全,关于这个问题,前端是无法完全杜绝的,必须后端进行兜底,这里不是今天的重点。

分类分析

提交按钮重复点击

这是最常见的问题,重复提交会造成多条数据入库。点击提交给个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的特点:

  1. 会阻塞
  2. 无论页面显示隐藏,都会执行
  3. 退出需要清除定时器
  4. 间隔时间可以任意定义
  5. 浏览器兼容性极好

那么有没有一种东西能解决setInterval的缺点?这个就是requestAnimationFrame,简称rAF。 rAF的特点:

  1. 按浏览器帧执行,低阻塞。如果是动画则流畅度会更高。
  2. 仅当页面显示时会执行,页面在后台不执行,这样可以减小CPU占用
  3. 退出需要清除定时器
  4. 间隔时间是每一帧的间隔时间,可以封装方法任意定义
  5. 浏览器兼容比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发出的请求,传个标记给后端,后端返回数据带给前端