1.前言
近期项目里的联想组件,与后端建立网络连接,为了保证弱网环境下的时序问题,使用的方案是axios提供的取消请求方案。可移步。因此在多次请求下,虽然前端做了防抖,但在弱网环境下,还是会在控制台会暴一堆红(取消请求),显得十分不优雅。并且后端同学反馈,因为建立的连接还没响应就被我们杀死了,会导致后端暴异常。
那么有没有更加优雅的方案?此处省略...好多文字。当然有!
2.方案思路
在请求中加一把锁,把同一个url的请求存在一个队列里,如果上一次请求还没完事,也就是锁还没开启时,我们将后续的请求存储在队列里,不触发。等请求完事后开锁,不就搞定了。可能有人会问,既然都不触发,那么还存个锤子的队列,直接return不就好了。这里有个问题,如果后面的请求参数和当前未结束的请求的参数不一致,那么就要发起最后一次请求保证最后一次响应的数据是用户需要的,这时这个队列就起作用啦。
3.主要数据结构
/** 请求锁
* 判断当前队列是否存在请求未结束的 */
isRequesting: { [key: string]: boolean } = {};
/** 请求队列
* key-唯一标识
*/
requestList: {
[key: string]: IRequest[];
} = {};
可以用请求路径(url)作为唯一标识。
4.主要代码
instance: AxiosInstance;
/** 请求队列
* key-唯一标识
*/
requestList: {
[key: string]: IRequest[];
} = {};
/** 请求锁 */
isRequesting: { [key: string]: boolean } = {};
constructor(defaultConfig: IAxiosRequestConfig = {}) {
this.instance = axios.create(defaultConfig);
}
/** 更新回调函数数组 */
setRequestList = (key: string, params: IRequest) => {
this.requestList = {
[key]: this.requestList[key] ? [...this.requestList[key], params] : [params],
};
};
/** 处理成功请求
* first-首次请求 last-最后一次请求 key-请求路径
*/
handleSuccess = ({ key, resolve, res: firstRes }: { key: string; resolve: any; res: any }) => {
this.isRequesting[key] = false;
const requestLen = this.requestList[key]?.length || 0;
if (requestLen >= 2) {
const first = this.requestList[key][0];
const last = this.requestList[key][requestLen - 1];
if (!_.isEqual(first.config, last.config)) {
// @ts-ignore
last.method<T>(last.config).then((lastRes: AxiosResponse<any>) => {
resolve(lastRes?.data);
});
} else {
resolve(firstRes.data);
}
} else {
resolve(firstRes.data);
}
this.requestList[key] = [];
};
/**
* 当多次发起请求并且第一次请求未结束时,若参数完全一致只允许首次请求,不一致在首次结束后,进行最后一次
*/
requestInterception<T>(config: IAxiosRequestConfig): Promise<T> {
const sentryParams = {
data: config.data,
params: config.params,
url: config.url,
};
const requestMethod = this.instance.request;
const key: string = config.url || '';
return new Promise((resolve, reject) => {
if (this.isRequesting[key]) {
this.setRequestList(key, { method: requestMethod, config });
} else {
this.isRequesting[key] = true;
this.setRequestList(key, { method: requestMethod, config });
requestMethod<T>(config)
.then((res: AxiosResponse<any>) => {
const { code, msg = '' } = res.data;
this.handleSuccess({
res,
key,
resolve,
});
})
.catch((e) => {
reject(e);
});
}
});
}
当然,这种方案不是适合所有场景,可能有些场景下就是允许,同样的请求存在。所以我们可以在axios的config加个自定义参数,当明确需要的时候,在走这种方案 否则就执行正常的请求方式,即可。
5.写在最后
当然可能这并不是最佳的方案,目前在我们项目生产环境运行一段时间,暂未有不好的反馈。有兴趣的可以试试。亲测好使。。。