使用axios拦截器实现简单接口缓存,一次请求告别后端(doge

heihei.jpg

问题

  在一些场景下,如大屏项目中有些接口的数据时效性并不强,或者说更新不频繁。在这种情况下每次去请求后端接口得到的都是一样的数据,变相中就增加了服务器压力,也影响用户体验。

  如果我们能把接口返回的数据进行储存,当我们需要再次请求同一个接口时,返回之前已经缓存的数据那岂不是直接告别后端?!而且整个交互效果将变得无比丝滑。

目标

  首先这么多接口我们不可能每个函数单独去处理,都是些重复代码啰嗦又影响代码整体可读性,不可取。我们想达到终级的效果是无需改变业务层面的代码,在发起请求后就有人悄无声息的把事情都做了,然后像平常请求接口一样返回数据。无感的使用,优雅的爽嘿嘿。

实现方案

  既然要整齐划一的解决请求问题,那么我们首先就想到了axios自带的拦截器,拦截器分为请求拦截与响应拦截,有了拦截器那实现的思路就非常简单了。

  1. 在请求拦截器中,也就是请求发送前判断是否当前请求已被缓存,若已经缓存则放弃请求返回缓存的数据
  2. 在响应拦截器中,也就是在响应回来时判断当前响应的结果是否已经被缓存,若是新的响应结果则加入缓存

  简单两句话就解决了,好!本文就此结束(doge) 。虽说如此,但这么就水一篇文章良心属实过意不去啊。让我们来写写看。

实现步骤

创建axiso实例与生成请求标识

既然是特殊的axios请求所以我们不能写全局配置,而是选择配置一个新的实例。同时我们需要生成一个唯一标识用于区分不同的请求。标识由url与请求参数组成。

import axios from "axios";

//创建axios实例
const instance = axios.create();
//生成请求标识
function generateRequestKey(config, isRes = false){
  let requestKey = config.url;
  // 响应返回的请求配置中的data是字符串,请求发送的请求配置中的data是对象
  if (isRes) {
    requestKey += (config.data ?? "") + (JSON.stringify(config.params) ?? "");
  } else {
    requestKey +=
      (JSON.stringify(config.data) ?? "") +
      (JSON.stringify(config.params) ?? "");
  }
  return requestKey;
}

搞定请求标识接下来正式处理拦截器

请求拦截

//请求拦截
instance.interceptors.request.use(
  (config) => {
    //获取当前请求的请求标识
    const key = generateRequestKey(config);
    // 如果当前请求已被缓存,则通过Promise.reject取消当前请求,
    //并返回一个包含请求标识的对象用于后续处理
    if (cacheMap.has(key)) {
      return Promise.reject({
        cached: true,
        key,
      });
    }

    //如果当前请求未被缓存,则正常发送请求
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

请求拦截

//响应拦截
instance.interceptors.response.use(
  (res) => {
    // 根据现实情况只缓存的正确响应
    if (res.data.code === "00") {
      //获取当前响应的请求标识
      const key = generateRequestKey(res.config, true);
      // 如果当前请求未被缓存,则缓存当前请求
      if (!cacheMap.has(key)) {

        //注意缓存结果时需要进行一次深拷贝,避免不同请求间的数据相互影响
        cacheMap.set(key, cloneDeep(res.data));
      }
    }

    return res.data;
  },
  (err) => {
    // 判断当前响应错误类型是否为需要返回请求
    if (err.cached) {
      // 返回缓存数据,同样需要进行一次深拷贝处理
      return Promise.resolve(cloneDeep(cacheMap.get(err.key)));
    } else {
      // 对响应错误做点什么
      return Promise.reject(err);
    }
  }

);

  至此我们就实现了使用axios拦截器缓存接口数据的功能。平常怎么请求接口就怎么请求,唯一不同是需要缓存的接口使用我们新创建的这个实例发起请求。直接撒花

扩展

对于缓存功能需求还有几个基本的需求:

  • 设置缓存过期时间,若缓存过期则发起新的请求获取最新数据
  • 支持手动更新缓存

其他的我没想到,基于这两个需求我们可以稍微完善一下

支持设置缓存过期时间

  思路很简单,在缓存的数据中加入缓存时的时间,每次读取缓存时判断请求是否过期,过期则发送新请求

//定义缓存过期时间
const cacheExpirationTime = 1000 * 60 * 60; //1小时更新一次


//响应拦截
instance.interceptors.response.use(
  (res) => {
    if (res.data.code === "00") {
      const key = generateRequestKey(res.config, true);
      if (!cacheMap.has(key)) {

        //在缓存中加入缓存时间,而非此前单纯的响应数据
         //cacheMap.set(key, cloneDeep(res.data));
        cacheMap.set(key, { cacheDate: new Date(), res: cloneDeep(res.data)});
      } else {
        // 若已存在缓存,并且缓存已过期,则更新缓存
        const requestCache = cacheMap.get(key);
        if (new Date() - requestCache.cacheDate > cacheExpirationTime) {
          cacheMap.set(key, {
            cacheDate: new Date(),
            res: cloneDeep(res.data),
          });
        }
      }
    }
    return res.data;
  },
  (err) => {
    if (err.cached) {
    //无需特殊处理
      return Promise.resolve(cloneDeep(cacheMap.get(err.key)));
    } else {
      // 对响应错误做点什么
      return Promise.reject(err);
    }
  }
);

//请求拦截
instance.interceptors.request.use(
  (config) => {
    const key = generateRequestKey(config);
    //读取请求缓存
    const requestCache = cacheMap.get(key);
    //缓存存在并且缓存并未过期,返回缓存,否则正常发送请求
    if (requestCache && new Date() - requestCache.cacheDate < cacheExpirationTime ) {
      return Promise.reject({
        cached: true,
        key,
      });
    }
    //如果当前请求未被缓存,则正常发送请求
    return config;
  },
  (error) => {
    return Promise.reject(error);

  }

);



手动清空缓存

懂的都懂

1158CFB7.jpg

export function clearCache() {
  cache.clear();
}

完整代码

最后贴上完整代码。新手上路,用词写法可能存在很多问题还望各位大佬指正

import axios from "axios";

const cloneDeep = require("clone-deep");


const instance = axios.create();

//定义缓存对象
//因为需要频繁读写数据,所以这里使用性能更高的map对象
const cacheMap = new Map();

//手动刷新刷新缓存
export function clearCache() {
  cache.clear();
}

//生成请求标识
function generateRequestKey(config, isRes = false) {
  let requestKey = config.url;
  // 响应返回的请求配置中的data是字符串,请求发送的请求配置中的data是对象
  if (isRes) {
    requestKey += (config.data ?? "") + (JSON.stringify(config.params) ?? "");
  } else {
    requestKey +=
      (JSON.stringify(config.data) ?? "") +
      (JSON.stringify(config.params) ?? "");
  }

  return requestKey;
}

//缓存时效
const cacheExpirationTime = 1000 * 60 * 60; //1小时更新一次

//请求拦截
instance.interceptors.request.use(
  (config) => {
    //获取当前请求的请求标识
    const key = generateRequestKey(config);
    //读取请求缓存
    const requestCache = cacheMap.get(key);
    //缓存存在并且缓存并未过期,返回缓存,否则正常发送请求
    if (
      requestCache &&
      new Date() - requestCache.cacheDate < cacheExpirationTime
    ) {
      return Promise.reject({
        cached: true,
        key,
      });
    }
    //如果当前请求未被缓存,则正常发送请求
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);

  }

);



//响应拦截
instance.interceptors.response.use(
  (res) => {
    if (res.data.code === "00") {
      const key = generateRequestKey(res.config, true);
      if (!cacheMap.has(key)) {
        cacheMap.set(key, { cacheDate: new Date(), res: cloneDeep(res.data) });
      } else {
        // 若已存在缓存,并且缓存已过期,则更新缓存
        const requestCache = cacheMap.get(key);
        if (new Date() - requestCache.cacheDate > cacheExpirationTime) {
          cacheMap.set(key, {
            cacheDate: new Date(),
            res: cloneDeep(res.data),
          });
        }
      }
    }
    return res.data;
  },
  (err) => {
    if (err.cached) {
      //返回缓存
      return Promise.resolve(cloneDeep(cacheMap.get(err.key)));
    } else {
      // 对响应错误做点什么
      return Promise.reject(err);
    }
  }
);

export default instance;


© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYJCR6Z3' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片