[工程架构思考-请求] cancel-previous-request-when-user-click-search-button

47 阅读2分钟

以比较常用的前端 request 库:axios 为例:

场景:

image

页面中有四个 Tab,当用户进入页面的时候,会加载默认tab 的数据。

当用户并不想看这个 tab 的数据,而是想切换到其它 tab的 时候,这时候就需要将前一个tab 的请求取消掉,否则可能会出现:

因为四个 Tab 的数据结构都相同,第二个 Tab 的请求数据量比较小,而第一个 Tab 的请求的数据量比较大,导致第一个 Tab的请求的 response 比 第二个请求的response 晚到。

页面中已经渲染好了 第二个 Tab的数据,结果用户在浏览第二个 Tab的数据的时候,第一个 Tab的数据这时候来了,把第二个 Tab的数据给覆盖了,这时候,用户就完全懵圈了,数据就完全错乱了

解决方案

当用户切换 Tab 的时候,需要将前一个 Tab 取消

思路

  1. 建立队列
  2. 为每一个请求生唯一ID(或者存储 请求的URL、请求参数等信息),将该请求 push 到这个 队列中

上面ID 的作用其实是,有时候页面可能会同时有一些和 Tab1、Tab2 无关的请求,我们并希望在取消 Tab1相关请求的时候,将无关请求也取消了。因此我们可以传入 ID,这样就能指定取消某个具体的请求了。当然根据 URL和参数来取消特定请求也是 OK 的

  1. 点击 Tab1, 将 Tab1 相关的请求push 到队列中;如果在请求过程中,用户点击了 Tab2,则遍历队列,调用请求的 cancel 方法,取消该请求

代理演示

// 取消重复请求
// pending: Array<{ url: string; cancel: Function; }>
const pending = [];
window.pendingRequests = pending;
const CancelToken = axios.CancelToken;
const removePending = config => {
  let pendingLen = pending.length;
  while (pendingLen--) {
  // for (const index in pending) {
    const item = pending[pendingLen];
    // 当前请求在数组中存在时执行函数体
    if (item.url === fmtUrlByConfig(config)) {
      // 执行取消操作
      item.cancel();
      // 从数组中移除记录
      pending.splice(pendingLen, 1);
    }
  }
};

// http request 拦截器
axios.interceptors.request.use(
  config => {
    removePending(config);
    config.cancelToken = new CancelToken(c => {
      pending.push({
        url: getUrlByPathAndParams(config),
        cancel: c,
      });
    });
    return config;
  },
  error => {
    // Do something with request error
    return Promise.reject(error);
  }
);