当前位置 主页 > 网站技术 > 代码类 >

    axios如何取消重复无用的请求详解

    栏目:代码类 时间:2019-12-15 18:07

    前言

    在开发中,经常会遇到接口重复请求导致的各种问题。

    对于重复的get请求,会导致页面更新多次,发生页面抖动的现象,影响用户体验。

    对于重复的post请求,会导致在服务端生成两次记录(例如生成两条订单记录)。

    如果当前页面请求还未响应完成,就切换到了下一个路由,那么这些请求直到响应返回才会中止。

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

    当然我们可以通过页面loading来避免用户进行下一次的操作,但本文只讨论单纯的如何取消这些无用的请求。

    axios 的 cancelToken

    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
     }
    });
    
    axios.post('/user/12345', {
     name: 'new name'
    }, {
     cancelToken: source.token
    })
    
    // 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()判断当前请求是否是主动取消的,以此来区分普通的异常逻辑。

    封装取消请求逻辑

    上面有两种取消请求,用哪种都是可以的,这里使用第二种。

    取消请求主要有两个场景:

    当请求方式method,请求路径url,请求参数(get为params,post为data)都相同时,可以视为同一个请求发送了多次,需要取消之前的请求 当路由切换时,需要取消上个路由中未完成的请求

    我们封装几个方法:

    // 声明一个 Map 用于存储每个请求的标识 和 取消函数
    const pending = new Map()
    /**
     * 添加请求
     * @param {Object} config 
     */
    const addPending = (config) => {
     const url = [
     config.method,
     config.url,
     qs.stringify(config.params),
     qs.stringify(config.data)
     ].join('&')
     config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
     if (!pending.has(url)) { // 如果 pending 中不存在当前请求,则添加进去
      pending.set(url, cancel)
     }
     })
    }
    /**
     * 移除请求
     * @param {Object} config 
     */
    const removePending = (config) => {
     const url = [
     config.method,
     config.url,
     qs.stringify(config.params),
     qs.stringify(config.data)
     ].join('&')
     if (pending.has(url)) { // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
     const cancel = pending.get(url)
     cancel(url)
     pending.delete(url)
     }
    }
    /**
     * 清空 pending 中的请求(在路由跳转时调用)
     */
    export const clearPending = () => {
     for (const [url, cancel] of pending) {
     cancel(url)
     }
     pending.clear()
    }

    Map是ES6中一种新型的数据结构,本身提供了诸多方法,方便操作,适合当前场景。如果不熟悉的可以查看ECMAScript 6 入门。