一、接口去重
为什么要进行接口去重?
在实际场景中,筛选面板的条件很多,用户可以频繁点击不同的筛选项,对用户不做任何防抖和节流的限制,有可能就会出现旧数据覆盖新数据。因为接口的响应时间不是固定的,比如我第一次点击,网络不好,接口5秒才返回数据,而我的第二次点击,网络很好,只需要2秒返回数据,那么实际上页面展示的数据可能是第一次调用返回的,就导致页面错乱。
怎么才能避免页面错乱?
只获取最新一次接口数据,取消其他接口请求。
1、创建存储对象
定义一个onlyDataCache对象,提供getRequestKey唯一标识、query查看、delete删除、deleteAll删除所有。唯一需要注意就是在getRequestKey中full-path标识,主要是判断唯一标识是否为全路径,在一些特殊场景,多次请求同一个接口,传参不同,不应该并定义为相同多次请求,而是不同多次请求,所以不应该去重。
核心主要是Axios.CancelToken中Canceler方法,要取消接口请求、直接触发Canceler方法,内部会报一个Cancel异常,可以通过catch区分处理一下。怎么区分判断是否为Cancel异常?通过Axios.isCancel()API判断。
//去重缓存数据
const onlyDataCache = {
// 缓存列表
list: [],
// 根据请求的信息(请求方式,url,请求get/post数据),产生map的key
getRequestKey(config) {
const fullPath = config.headers["full-path"];
const { method, url, params, data } = config;
var key = [method, url].join("&");
if (fullPath) {
key = [method, url, stringify(params), stringify(data)].join("&");
}
return key;
},
query() {
console.log("onlyData=" + JSON.stringify(this.list));
},
add(config, cancel) {
var key = this.getRequestKey(config);
//console.log("onlyData add key=" + key);
var index = this.list.findIndex(t => t.key === key);
if (index > -1) {
this.list[index].cancel();
this.list[index].cancel = cancel;
} else {
// 添加到缓存列表中
this.list.push({ key, cancel });
}
},
delete(config) {
var targetKey = this.getRequestKey(config);
//console.log("onlyData delete key=" + targetKey);
if (targetKey) {
this.list.forEach((item, index) => {
if (item.key === targetKey) {
item.cancel();
this.list.splice(index, 1);
}
});
}
},
deleteAll() {
this.list.forEach((item, index) => {
item.cancel();
this.list.splice(index, 1);
});
}
};
2、请求拦截去重处理
在请求接口时,会在头部headers带上一个only-data标识,主要判断当前请求是否要进行去重。先对同一个接口进行cancel()取消,并保存当前请求Canceler方法,方便下一次进行cancel()取消。
if (config.headers) {
//判断是否需要去重
const onlyData = config.headers["only-data"];
if (onlyData) {
//console.log("删除接口onlyData缓存数据!");
onlyDataCache.delete(config);
config.cancelToken = new Axios.CancelToken(cancel => {
//console.log("保存接口onlyData缓存数据!");
onlyDataCache.add(config, cancel);
});
}
}
3、响应拦截Cancel错误处理
为什么会出现Cancel错误?
当对接口请求就行cancel()取消,内部会生成一个Cancel类对错误信息进行包装,并抛给用户处理。如果不进行catch,控制台直接报错,虽然不影响功能使用,最好还是捕获一下。
怎么处理Cancel错误?
在axios 响应拦截器中AxiosError处理。
// 所有的响应异常 区分来源为取消请求/非取消请求
if (Axios.isCancel(error)) {
} else {
return Promise.reject(error);
}
4、使用
调用接口时候直接在headers 传标识,默认不传为false。
headers: {
"only-data": true
}
二、数据持久化
1、创建存储对象
定义一个keepDataCache对象,提供getRequestKey唯一标识、query查看、add增加、find查找,并且需要设置缓存数和缓存时间,在add()保存缓存数据,如果已经存在,直接覆盖旧数据,find()获取缓存数据,需判断是否存在,是否过期无效。
// 持久化缓存数据
const keepDataCache = {
// 缓存列表
list: [],
// 最大缓存数
MAX_NUM: 100,
// 最大缓存时间
EXPIRED_TIME: 60000,
// 根据请求的信息(请求方式,url,请求get/post数据),产生map的key
getRequestKey(config) {
const { method, url, params, data } = config;
const key = [method, url, stringify(params), stringify(data)].join("&");
return key;
},
query() {
console.log("keepData=" + JSON.stringify(this.list));
},
// 添加缓存结果
add({ config, data }) {
if (config.data) config.data = JSON.parse(config.data);
var key = this.getRequestKey(config);
//console.log("keepData add key=" + key);
var index = this.list.findIndex(t => t.key === key);
if (index > -1) {
// 保存请求结果
this.list[index].data = data;
} else {
// 添加到缓存列表中
this.list.push({ time: Date.now(), key, data });
}
},
// 查找缓存结果
find(config) {
// 根据请求信息生成key
var key = this.getRequestKey(config);
//console.log("keepData find key=" + key);
var index = this.list.findIndex(t => t.key === key);
// 判断缓存当中是否有该请求结果
if (index > -1) {
let data = this.list[index];
// 判断是否超出了最大缓存时间
if (Date.now() - data.time > this.EXPIRED_TIME) {
// 清除该缓存
this.list.splice(index, 1);
} else {
// 返回缓存
return data;
}
}
// 判断是否超出了最大缓存数量
if (this.list.length === this.MAX_NUM) {
this.list.shift();
}
// 返回undefined,让请求拦截不执行config.adapter
return undefined;
}
};
2、请求拦截获取缓存数据
在请求接口时,会在头部headers带上一个keep-data标识,主要判断当前请求是否要获取缓存。通过axios请求拦截器,设置config.adapter自定义处理请求,设置config.adapter后,请求会被拦截,不会再向服务器发请求。
if (config.headers) {
//判断是否需要持久化,并判断是否有缓存数据
const keepData = config.headers["keep-data"];
if (keepData) {
//console.log("加载接口keepData缓存数据!");
var cacheData = keepDataCache.find(config);
// 查看缓存当中有没有
if (cacheData) {
//console.log("加载成功过,keepData有缓冲数据!");
// 通过config.adapter,允许自定义处理请求
config.adapter = function (config) {
return new Promise(resolve => {
const res = {
status: 200,
statusText: "",
headers: { "content-type": "application/json" },
config,
request: {}
};
resolve({ ...res, data: cacheData.data });
});
};
}
}
}
3、响应拦截保存网络数据
对服务器状态、接口状态进行判断,并对数据进行缓存。
const config = response.config;
if (response) {
const keepData = config.headers["keep-data"];
if (keepData) {
if (response.status === 200 && response.data.code === 0) {
if (
response.headers["content-type"] &&
response.headers["content-type"].indexOf("application/json") >=
0 &&
response.request.responseType != "blob"
) {
//console.log("保存接口keepData缓存数据!");
//缓存结果到缓存中
keepDataCache.add(response);
}
}
}
}
4、使用
调用接口时候直接在headers 传标识,默认不传为false。
headers: {
"keep-data": true
}
注:就是所有接口请求,对数据进行缓存,或者获取缓存数据,都是针对于content-type为application/json情况下。