👀如何给你的全局Axios实例添加请求缓存

818 阅读4分钟

背景

一个按钮如果被频繁点击,就会连续发送多个请求,这个场景往往可以使用防抖策略处理,不过这个方案需要在事件上手动引入,比较麻烦。如果要针对Axios的XHR请求做一个全局优化,让每个request请求都可以做成可配置化的缓存,n秒内重新触发相同事件不再发送请求,而是直接使用上次缓存回传的响应数据。这样也可以实现类防抖化的处理,只是我们将这段逻辑做成了全局可配置化的,复用起来很方便。

实现

大家都知道,axios在项目通常都做了相应的封装,一般都将封装逻辑放在requst.js文件内,最后导出一个axios实例对象,然后通过import的方式直接使用封装后的axios。因此我们的缓存功能可以结合axios封装逻辑去开发。

下面我们一起来看下整个开发过程:

1. 获取key

首先我们必须要确定一点,既然是缓存,缓存对象是每个请求,因此就必须要标识每个请求的唯一key值,key值具有唯一性,所以我们必须制作一个生成请求key的方法。

request.js

// 根据请求的信息产生对应的key值
import qs from 'qs';
function getRequestKey(config) {
    const { method, url, params } = config;
    if (!['GET'].includes(method.toUpperCase())) return; // 由于是缓存响应数据,因此这里只处理Get请求
    return [method.toLowerCase(), url, qs.stringify(params)].join('&');
}

以上代码即可生成一个请求标识符Key,下面都会针对这个Key来识别缓存的目标请求身份。

2. 在响应拦截器中根据Key缓存响应数据

在响应拦截器中,我们可以获取到responseData,这部分内容是我们要缓存的目标数据。在缓存期间重新发起的请求都不会走网络请求,而是直接从缓存中获取本次缓存的数据展示在页面上。

    // 防抖操作
    function debounce(fun, delay) {
      // let timer = null;
      return function (args) {
        const _that = this;
        const _args = args;
        clearTimeout(fun.id);
        fun.id = setTimeout(() => {
          fun.call(_that, _args);
        }, delay);
      };
    }
    // 响应拦截器逻辑省略···
    let reqKey = getRequestKey(axiosConfig);
    if (reqKey) {
      const index = reqKey.indexOf(`https://${window.location.host}/api`);
      if (index !== -1) {
        const reqKeyArr = reqKey.split('');
        reqKeyArr.splice(index, `https://${window.location.host}/api`.length);
        reqKey = reqKeyArr.join('');
      }
      function remove(key) {
        clearOneSession(key);
      }
      (() => {
        setSession(reqKey, res.data); // 在响应拦截器中添加可以直接缓存响应数据
        debounce(remove, 5000)(reqKey); // 设置缓存存活时间5s,时间设置要适中——太长会导致数据过期,时间太短缓存意义就不大了...
      })();
    }

首先获取请求的唯一Key值,判断Key是否包含目标域名(通常是接口地址前缀),这个判断是为了筛选要缓存的请求必须是XHR类型请求,静态资源请求不考虑。

如果校验通过,将key中的公共url前缀部分截掉,避免key值过于长。

最后Key值形如get[请求类型]&/service/xxx[截取后的接口路径]&param=1&param2=2[请求参数]

这个时候我们完成了发出请求回来的响应数据的缓存,接着我们需要在合适的地方判断应该使用缓存还是重新发送新的请求获取最新数据。

3. 装饰request实例

需要注意的是,为了不影响原来逻辑且功能可配置化,应该给每个reqeust添加一个cache属性,默认为false(不缓存),如果需要使用请求缓存功能,在接口调用的地方手动引入cache: true即可。

request({
    ...
    cache: true,
})

使用装饰器模式,针对原来的axios实例进行包装,引入对应的缓存校验与获取功能。

function serviceAxios(request) {
// cache逻辑
  if (request.cache) {
    let reqKey = getRequestKey(request);
    const index = reqKey && reqKey.indexOf(`https://${window.location.host}/api`);
    if ((reqKey || reqKey === 0) && index !== -1) {
      const reqKeyArr = reqKey.split('');
      reqKeyArr.splice(index, `https://${window.location.host}/api`.length);
      reqKey = reqKeyArr.join('');
    }
    // 存在key且缓存未过期,使用缓存
    if (reqKey && hasSession(reqKey)) {
      return Promise.resolve(getSession(reqKey));
    }
  }
  // 真实请求
  return service({ ...request });
}

通过上述配置,5s内重新发起相同的请求,会直接从sessionStorage中获取上次缓存的响应数据,而不会发起新的请求。5s后自动发起新的请求,更新缓存数据。

具体缓存的内容,可以去控制台的Application中进行查看,以验证该方案的可行性。

总结

在axios的全局配置文件中添加请求缓存,可以减少频繁请求后端查询接口,减轻服务端的压力。同时也可以避免前端出现大量的的http并发请求,造成浏览器卡顿。

同时由于该方案采用「装饰器模式」,针对全局的axios实例进行了功能增强,同时可配置化的引入,开发者可以自由决定是否要使用缓存方案,而无需关注具体实现方案,对全局代码不具备任何侵入性。

需要注意的是,该方案仅适用于GET请求,适合在表格查询、数据回显等场景下使用,如果要针对特定情况的POST请求进行「缓存」,采用表单提交常用的防抖策略可能更加合适。

全文完。