概述
axios库自身曾经很好应用了。然而具体到业务层面,会波及到几个十分高频触发的情景须要提取解决。最罕用的可能如下:
- 勾销反复的申请。(频繁操作或者state频繁更新导致组件频繁render触发的多次重复申请)
- 失败主动发动重试。(因为网络稳定或者服务器不稳固起因,重发可进步成功率的状况)
- 主动缓存申请后果。(对于实时性要求不高的接口,能够缓存后果,防止申请)
思路实现
勾销
对于axios的勾销机制原理,有一篇文章剖析过了,有趣味的能够跳转查看axios的cancelToken勾销机制原理
剖析:
- 首先要对立治理多个申请,所以比拟容易想到 应用一个队列来治理申请,而后思考到每个申请的唯一性,就天然会想到Map数据结构。
- Map申请治理数据结构增加和删除的机会,天然是利用axios提供的申请/响应拦截器来解决。
- 而后每次开始申请和完结申请的时候,检测Map数据申请构造,如果有进行清理。
重试
重试retry的话,在上述根底上,在响应拦截器里监听如果 error失败,那么再次发动申请即可
缓存
cache性能应该是最简略的,只须要在 响应拦截器里缓存response,而后在每次开始申请的时候,检测是否有缓存的response,如果有,则间接返回即可。
技术实现
主动勾销
先实现主动勾销性能,根据上述剖析,咱们首先须要一个Map数据结构来存储申请。代码如下:
class MAxios {
private requestMap = new Map();
}
而后咱们须要一个对外的启动申请的接口函数,承受一些配置项参数。
在此函数里,咱们须要实例化axios申请,为了方便管理,使每个申请间的配置互不烦扰,咱们采纳一个申请一个实例的治理。
此外还须要对每个申请增加 申请、响应拦截器。
伪代码如下:
class MAxios {
private requestMap = new Map();
private interceptorsRequest(instance: AxiosInstance) {}
private interceptorsResponse(instance: AxiosInstance) {}
private interceptors(instance: AxiosInstance) {
this.interceptorsRequest(instance);
this.interceptorsResponse(instance);
}
/**
* 实例工厂函数
*/
private getAxiosInstance(config: IConfig) {
const instance = axios.create();
return instance;
}
public request(options: IConfig) {
const config = Object.assign({}, defaultConfig, options);
// 工厂函数获取axios实例
const instance = this.getAxiosInstance();
// 增加拦截器
this.interceptors(instance);
// 返回申请
return instance.request(config);
}
}
接下来咱们着重实现 拦截器细节。对申请拦截器和响应拦截器别离剖析。
- 申请拦截器
咱们须要首先去Map构造里查看是否有缓存,有的话移除,而后增加新的 - 响应拦截器
申请胜利或者失败之后,都须要移除Map缓存的申请
Map缓存的key,能够提供个接口给开发者自定义或者内置一个默认策略,比方依据method和url来生成。
参考代码:
/**
* 申请拦截器
* @param {*} instance
*/
private interceptorsRequest(instance: AxiosInstance) {
instance.interceptors.request.use(
(config) => {
this.removeReq(config);
this.addReq(config);
return config;
},
(error) => {
// Do something with request error
return Promise.reject(error);
}
);
}
/**
* 响应拦截器
* @param {*} instance
*/
private interceptorsResponse(instance: AxiosInstance) {
instance.interceptors.response.use(
(response) => {
this.removeReq(response.config);
return response;
},
(error) => {
const config = error.config || {};
this.removeReq(config);
if (axios.isCancel(error)) {
return Promise.reject(error.message);
} else {
return Promise.reject(error);
}
}
);
}
/**
* 生成标识申请的惟一key
*/
getDuplicatedKey(config) {
const { duplicatedKey = () => "" } = config;
return duplicatedKey(config);
}
/**
* 增加申请
*/
addReq(config) {
const { cancelDuplicated } = config;
if (!cancelDuplicated) {
return;
}
const key = this.getDuplicatedKey(config);
if (!this.requestMap.has(key)) {
config.cancelToken = new axios.CancelToken((c) => {
this.requestMap.set(key, c);
});
}
}
/**
* 移除申请
*/
removeReq(config) {
try {
const { cancelDuplicated } = config;
const key = this.getDuplicatedKey(config);
if (!cancelDuplicated) {
return;
}
if (!this.requestMap.has(key)) {
return;
}
const cancel = this.requestMap.get(key);
this.requestMap.delete(key);
cancel({
type: ERROR_TYPE.Cancel,
message: "Request canceled ",
});
} catch (error) {
console.log("removeReq: ", error);
}
}
到这里勾销申请的性能就实现了。
主动重试
重试的逻辑非常简单,只须要在失败的时候判断一下重试次数,而后依据条件再次发动申请即可,如下:
/**
* 响应拦截器
* @param {*} instance
*/
private interceptorsResponse(instance: AxiosInstance) {
instance.interceptors.response.use(
(response) => {
//balabala
},
(error) => {
// retry 重试逻辑
if (config.retry > 0) {
return this.retry({ instance, config, error });
}
return Promise.reject(error);
}
}
);
}
/**
* 重试某次申请
*/
private retry({
instance,
config,
error
}: {
instance: AxiosInstance;
config: IConfig;
error: AxiosError;
}) {
const { retry, retryDelay, retryDelayRise } = config;
let retryCount = config.__retryCount || 0;
config.__retryCount = retryCount;
// 查看是否超过重置次数
if (retryCount >= retry!) {
return Promise.reject(error);
}
// 重发计数器加1
retryCount += 1;
config.__retryCount = retryCount;
if (retryCount === retry) {
config.timeout = 15000;
}
// 延时重发
let delay = 0;
if (typeof retryDelay === 'number') {
delay = retryDelay * (retryDelayRise ? retryCount : 1);
} else {
delay = retryDelay!(retryCount);
}
const retryTask = new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, delay);
});
return retryTask.then(() => {
return instance.request(config);
});
}
OK,是不是非常简单?
缓存后果
缓存是最简略的性能了,间接贴代码:
private responseCacheMap = new Map();
/**
* 获取response缓存
* @param {*} config
*/
private getResponseCache(config: IConfig) {
const key = this.getDuplicatedKey(config);
if (this.responseCacheMap.has(key)) {
return this.responseCacheMap.get(key);
}
return null;
}
/**
* 设置response缓存
* @param {*} config
* @param {*} response
*/
private setResponseCache(config: IConfig, response: AxiosResponse) {
if (!config.cache) {
return;
}
const key = this.getDuplicatedKey(config);
this.responseCacheMap.set(key, response);
}
public request(options: IConfig) {
const config = Object.assign({}, defaultConfig, options);
if (config.cache) {
const responseCache = this.getResponseCache(config);
if (responseCache) {
return Promise.resolve(responseCache);
}
}
// balabala
}
OK,GameOver!
上面贴上残缺代码:
import axios, { AxiosRequestConfig, Method, AxiosInstance, AxiosResponse, AxiosError } from 'axios';
type IRetryDelay = number | ((c: number) => number);
interface IConfig extends AxiosRequestConfig {
cancelDuplicated?: boolean;
duplicatedKey?: (ops: IConfig) => string;
retry?: number;
retryDelay?: IRetryDelay;
retryDelayRise?: boolean;
cache?: boolean;
__retryCount?: number;
}
const defaultConfig: IConfig = {
method: 'get',
cancelDuplicated: false,
duplicatedKey: ({ method, url }) => `${(method as Method).toLocaleLowerCase()}${url}`,
retry: 0,
retryDelay: 200,
retryDelayRise: true,
cache: false
};
const ERROR_TYPE = {
Cancel: 'cancelDuplicated'
};
class MAxios {
public name: string = 'MAxios';
private requestMap = new Map();
private responseCacheMap = new Map();
/**
* 生成标识申请的惟一key
*/
private getDuplicatedKey(config: IConfig) {
const { duplicatedKey = () => '' } = config;
return duplicatedKey(config);
}
/**
* 增加申请
*/
private addReq(config: IConfig) {
const { cancelDuplicated } = config;
if (!cancelDuplicated) {
return;
}
const key = this.getDuplicatedKey(config);
if (!this.requestMap.has(key)) {
config.cancelToken = new axios.CancelToken((c) => {
this.requestMap.set(key, c);
});
}
}
/**
* 移除申请
*/
private removeReq(config: IConfig) {
try {
const { cancelDuplicated } = config;
const key = this.getDuplicatedKey(config);
if (!cancelDuplicated) {
return;
}
if (!this.requestMap.has(key)) {
return;
}
const cancel = this.requestMap.get(key);
this.requestMap.delete(key);
cancel({
type: ERROR_TYPE.Cancel,
message: 'Request canceled '
});
} catch (error) {
console.log('removeReq: ', error);
}
}
/**
* 重试某次申请
*/
private retry({
instance,
config,
error
}: {
instance: AxiosInstance;
config: IConfig;
error: AxiosError;
}) {
const { retry, retryDelay, retryDelayRise } = config;
let retryCount = config.__retryCount || 0;
config.__retryCount = retryCount;
// 查看是否超过重置次数
if (retryCount >= retry!) {
return Promise.reject(error);
}
// 重发计数器加1
retryCount += 1;
config.__retryCount = retryCount;
if (retryCount === retry) {
config.timeout = 15000;
}
// 延时重发
let delay = 0;
if (typeof retryDelay === 'number') {
delay = retryDelay * (retryDelayRise ? retryCount : 1);
} else {
delay = retryDelay!(retryCount);
}
const retryTask = new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, delay);
});
return retryTask.then(() => {
return instance.request(config);
});
}
/**
* 获取response缓存
* @param {*} config
*/
private getResponseCache(config: IConfig) {
const key = this.getDuplicatedKey(config);
if (this.responseCacheMap.has(key)) {
return this.responseCacheMap.get(key);
}
return null;
}
/**
* 设置response缓存
* @param {*} config
* @param {*} response
*/
private setResponseCache(config: IConfig, response: AxiosResponse) {
if (!config.cache) {
return;
}
const key = this.getDuplicatedKey(config);
this.responseCacheMap.set(key, response);
}
/**
* 实例工厂函数
*/
private getAxiosInstance(config: IConfig) {
const instance = axios.create();
return instance;
}
/**
* 申请拦截器
* @param {*} instance
*/
private interceptorsRequest(instance: AxiosInstance) {
instance.interceptors.request.use(
(config) => {
// Do something before request is sent
this.removeReq(config);
this.addReq(config);
return config;
},
(error) => {
// Do something with request error
return Promise.reject(error);
}
);
}
/**
* 响应拦截器
* @param {*} instance
*/
private interceptorsResponse(instance: AxiosInstance) {
instance.interceptors.response.use(
(response) => {
// Do something with response data with status code 2xx
this.removeReq(response.config);
this.setResponseCache(response.config, response);
return response;
},
(error) => {
const config = error.config || {};
this.removeReq(config);
if (axios.isCancel(error)) {
return Promise.reject(error.message);
} else {
// retry 重试逻辑
if (config.retry > 0) {
return this.retry({ instance, config, error });
}
return Promise.reject(error);
}
}
);
}
private interceptors(instance: AxiosInstance) {
this.interceptorsRequest(instance);
this.interceptorsResponse(instance);
}
/**
* public request 对外接口
* @param {*} config 以后申请的配置参数
*/
public request(options: IConfig) {
const config = Object.assign({}, defaultConfig, options);
if (config.cache) {
const responseCache = this.getResponseCache(config);
if (responseCache) {
return Promise.resolve(responseCache);
}
}
const instance = this.getAxiosInstance(config);
this.interceptors(instance);
return instance.request(config);
}
}
export default new MAxios();
源码获取
想间接下载应用源代码的,能够跳转github仓库,蕴含javascript和Typescript两个版本的。
源代码获取
发表回复