美文网首页
Axios如何取消重复请求

Axios如何取消重复请求

作者: 前端技术驿站 | 来源:发表于2021-07-06 22:11 被阅读0次

    在实际开发中,我们需要对用户发起的重复请求进行拦截处理,比如用户快速点击提交按钮。

    对于重复的 get 请求,会导致页面更新多次,发生页面抖动的现象,影响用户体验;对于重复的 post 请求,会导致在服务端生成两次记录(例如生成两条订单记录)。

    无论从用户体验或者从业务严谨方面来说,取消无用的请求是需要避免的。

    一、一般处理方式

    我们可以在用户即将发送请求,但还未发送请求时给页面添加一个 loading 效果,提示数据正在加载,loading 会阻止用户继续操作。

    这种方式在大部分情况下是可行的,但是在某些情况下却不奏效,比如在 loading 显示之前,用户就已经触发了两次请求的情况。

    二、Axios 拦截器统一处理

    重复发送的请求的场景很多,我们需要在一个公共的地方对请求响应进行处理,Axios 拦截器就闪亮登场了。
    Axios 拦截器包括请求拦截器和响应拦截器,可以在请求发送前或响应后进行拦截处理,用法如下:

    // 添加请求拦截器
    axios.interceptors.request.use(
      function (config) {
        // 在发送请求之前做些什么
        return config;
      },
      function (error) {
        // 对请求错误做些什么
        return Promise.reject(error);
      }
    );
    
    // 添加响应拦截器
    axios.interceptors.response.use(
      function (response) {
        // 对响应数据做点什么
        return response;
      },
      function (error) {
        // 对响应错误做点什么
        return Promise.reject(error);
      }
    );
    

    那么,如何进行拦截呢?也就是如何取消用户的请求,将它扼杀在摇篮里...

    2.1、如何取消请求

    众所周知,浏览器是通过 XMLHttpRequest 对象进行 http 通信的,如果要取消请求的话,我们可以通过调用 XMLHttpRequest 对象上的 abort 方法来取消请求。

    let xhr = new XMLHttpRequest();
    xhr.open("GET", "http://www.shanzhonglei.com/", true);
    xhr.send();
    setTimeout(() => xhr.abort(), 300);
    

    Axios是一个主流的http请求库,它提供了两种取消请求的方式。

    第一种,通过axios.CancelToken.source生成取消令牌token和取消方法cancel。

    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
    
    axios.get('/user/12345', {
      cancelToken: source.token
    }).catch(function(thrown) {
      if (axios.isCancel(thrown)) {
        console.log('Request canceled', thrown.message);
      } else {
        // handle error
      }
    });
    
    // cancel the request (the message parameter is optional)
    source.cancel('Operation canceled by the user.');
    

    第二种,通过axios.CancelToken构造函数生成取消函数。

    const CancelToken = axios.CancelToken;
    let cancel;
    
    axios.get('/user/12345', {
      cancelToken: new CancelToken(function executor(c) {
        // An executor function receives a cancel function as a parameter
        cancel = c;
      })
    });
    
    // cancel the request
    cancel();
    

    需要注意的是在catch中捕获异常时,应该使用axios.isCancel()判断当前请求是否是主动取消的,以此来区分普通的异常逻辑。

    知道了如何取消请求就好办了,如果两个请求是相同的,那么我们就可以对后一个请求进行拦截操作。

    2.2、判断重复请求

    我们可以把每个请求的方法、url 和参数组合成一个字符串,作为该请求的唯一标识 key,与此同时,为对应的 key 生成一个 CancelToken 以备取消当前的请求。把 key 和对应的 cancel 函数以键值对的形式保存在 Map 对象中。

    const pendingRequest = new Map();
    const requestKey = [
      method,
      url,
      JSON.stringify(params),
      JSON.stringify(data),
    ].join("&");
    const cancelToken = new CancelToken(function executor(cancel) {
      if (!pendingRequest.has(requestKey)) {
        pendingRequest.set(requestKey, cancel);
      }
    });
    

    定义pendingRequests 为 map 对象的目的是为了方便我们查询它是否包含某个 key,以及添加和删除 key。

    在请求拦截器中,会检查pendingRequests 对象中是否包含当前请求的 requestKey,如果重复,就cancel拦截掉当前请求,如果不重复,则将requestKey 添加到 pendingRequests 对象中。

    2.3 具体实现

    我们先来生成几个辅助函数:

    generateReqKey:用于根据当前请求的信息,生成请求 Key

    function generateReqKey(config) {
      const { method, url, params, data } = config;
      return [method, url, JSON.stringify(params), JSON.stringify(data)].join("&");
    }
    

    addPendingRequest:用于把当前请求信息添加到 pendingRequest 对象中

    const pendingRequest = new Map();
    function addPendingRequest(config) {
      const requestKey = generateReqKey(config);
      config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
        if (!pendingRequest.has(requestKey)) {
           pendingRequest.set(requestKey, cancel);
        }
      });
    }
    

    removePendingRequest:检查是否存在重复请求,若存在则取消已发的请求

    function removePendingRequest(config) {
      const requestKey = generateReqKey(config);
      if (pendingRequest.has(requestKey)) {
        const cancelToken = pendingRequest.get(requestKey);
        cancelToken(requestKey);
        pendingRequest.delete(requestKey);
      }
    }
    

    clearPending 清空 pending 中的请求(在路由跳转时调用)

    function clearPending() {
      for (const [requestKey, cancelToken] of pendingRequest) {
        cancelToken(requestKey)
      }
      pendingRequest.clear()
    }
    

    实操来了...

    请求拦截器

    axios.interceptors.request.use(
      function (config) {
        removePendingRequest(config); // 检查是否存在重复请求,若存在则取消已发的请求
        addPendingRequest(config); // 把当前请求信息添加到pendingRequest对象中
        return config;
      },
      (error) => {
        // 这里出现错误可能是网络波动造成的,清空 pendingRequests 对象
        pendingRequests.clear();
        return Promise.reject(error);
      }
    );
    

    响应拦截器

    在这里,说明请求已经结束了,状态已经变成pending,这时需要把它从pendingRequests删除。

    axios.interceptors.response.use(
      (response) => {
        removePendingRequest(response.config); // 从pendingRequest对象中移除请求
        return response;
      },
      (error) => {
        removePendingRequest(error.config || {}); // 从pendingRequest对象中移除请求
        if (axios.isCancel(error)) {
          console.warn(error);
          return Promise.reject(error);
        } else {
          // 添加其它异常处理
        }
        return Promise.reject(error);
      }
    );
    

    最后,我们要在页面切换之前取消上一个路由中未完成的请求,清空缓存的pendingRequest对象。

    router.beforeEach((to, from, next) => {
      clearPending();
      // ...
      next();
    });
    

    最后

    关注公众号【前端技术驿站】让我们共同进步吧!我整理了一些项目实战视频,欢迎来学习!

    相关文章

      网友评论

          本文标题:Axios如何取消重复请求

          本文链接:https://www.haomeiwen.com/subject/oldnultx.html