问题
在一些场景下,如大屏项目中有些接口的数据时效性并不强,或者说更新不频繁。在这种情况下每次去请求后端接口得到的都是一样的数据,变相中就增加了服务器压力,也影响用户体验。
如果我们能把接口返回的数据进行储存,当我们需要再次请求同一个接口时,返回之前已经缓存的数据那岂不是直接告别后端?!而且整个交互效果将变得无比丝滑。
目标
首先这么多接口我们不可能每个函数单独去处理,都是些重复代码啰嗦又影响代码整体可读性,不可取。我们想达到终级的效果是无需改变业务层面的代码,在发起请求后就有人悄无声息的把事情都做了,然后像平常请求接口一样返回数据。无感的使用,优雅的爽嘿嘿。
实现方案
既然要整齐划一的解决请求问题,那么我们首先就想到了axios自带的拦截器,拦截器分为请求拦截与响应拦截,有了拦截器那实现的思路就非常简单了。
- 在请求拦截器中,也就是请求发送前判断是否当前请求已被缓存,若已经缓存则放弃请求返回缓存的数据
- 在响应拦截器中,也就是在响应回来时判断当前响应的结果是否已经被缓存,若是新的响应结果则加入缓存
简单两句话就解决了,好!本文就此结束(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);
}
);
手动清空缓存
懂的都懂
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;
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END