面向对象
虽然对面向对象理解不够深,还是尝试使用面向对象的方法进行封装。所以,选择使用TypeScript作为编程语言。主要的类关系如下图:
AxiosSugar就是最主要的类,它依赖外部传入axios(或axios的实例),AxiosSugarConfig(配置),AxiosSugarLifeCycle(生命周期/回调函数),AxiosSugarStorage(存储类)。
由于主要使用拦截器实现功能,所以该类只是一个“外部装载”的类,它并不暴露方法,仅仅用于添加拦截器和保存它依赖的类的实例。
interface AxiosSugarOptions {
config?: AxiosSugarConfig;
storage?: AxiosSugarStorage;
lifecycle?: AxiosSugarLifeCycle;
}
export default class AxiosSugar {
private stack: AxiosSugarRequestStack = new AxiosSugarRequestStack();
axios: AxiosInstance | AxiosStatic;
config: AxiosSugarConfig = new AxiosSugarConfig();
storage: AxiosSugarStorage = new AxiosSugarInnerStorage();
lifecycle: AxiosSugarLifeCycle = new AxiosSugarLifeCycle();
constructor (axios: AxiosInstance | AxiosStatic, options: AxiosSugarOptions = {}) {
this.axios = axios;
['config', 'storage', 'lifecycle'].forEach(option => {
if (options[option]) {
this[option] = options[option];
}
});
this.init();
}
private init (): void {
// 设置拦截器
requestInterceptors(this, this.stack);
responseInterceptors(this, this.stack);
}
}
派生Storage类
为了支持多种存储方式,又不希望最原始的Storage类可以实例化,将它设为接口,然后它的子类去实现它。
AxiosSugarStorage是一个接口,它指定三个必须实现的方法set, get, contains
,然后其它的类去实现它。
export interface AxiosSugarStorage {
set (symbol: string, res: any): void;
get (symbol: string): any;
contains (symbol: string): boolean;
}
export class AxiosSugarInnerStorage implements AxiosSugarStorage {
data: {[key: string]: any} = {};
set (symbol: string, res: any) {
this.data[symbol] = res;
}
get (symbol: string): any {
return this.data[symbol] || null;
}
contains (symbol: string): boolean {
return typeof this.data[symbol] !== 'undefined';
}
}
export class AxiosSugarInnerReleaseStorage extends AxiosSugarInnerStorage {
// save time
duration: number = 5 * 60 * 1000; // 5 minutes
// volume limit
limit: number = 15 * 1024 * 1024; // 15MB
constructor (duration: number, limit: number) {
super();
if (isDef(duration)) this.duration = duration;
if (isDef(limit)) this.limit = limit;
}
set (symbol: string, res: any) {
let data = this.data;
for (const [key, item] of Object.entries(data)) {
if (getDurationMS(new Date().getTime(), item.time) >= this.duration) {
delete data[key];
}
}
if (sizeof(res) + sizeof(data) > this.limit) {
data = this.data = {};
}
data[symbol] = {
data: res,
time: new Date().getTime()
};
}
get (symbol: string): any {
const target = this.data[symbol];
return target ? target.data : null;
}
}
export class AxiosSugarLocalStorage implements AxiosSugarStorage {
set (symbol: string, res: any) {
try {
localStorage.setItem(symbol, JSON.stringify(res))
} catch (err) {
console.error(`[axios-sugar]: ${err.message}`)
}
}
get (symbol: string) {
const data = localStorage.getItem(symbol)
return data === null ? null : JSON.parse(data)
}
contains (symbol: string) {
return this.get(symbol) !== null
}
}
栈类Stack
为了实现取消重复请求,所以需要一个类来存储每一个请求。请求开始时将其入栈,请求完成后将该请求出栈。然后在请求开始时判断是否栈中有相同的请求,就取消这个请求的发送。
export default class AxiosSugarRequestStack {
private confs: AxiosRequestConfig[] = [];
push (conf: AxiosRequestConfig) {
this.confs.push(conf);
}
contains (conf: AxiosRequestConfig): boolean {
return this.confs.indexOf(conf) >= 0;
}
remove (conf: AxiosRequestConfig) {
const confs = this.confs;
return confs.splice(confs.indexOf(conf), 1);
}
forEach (cb) {
this.confs.forEach((conf, confIdx, thisArg) => {
cb.call(conf, conf, confIdx, thisArg);
});
}
}
LifeCycle
为了对一些特定时期进行控制,需要一些回调函数的存在,现在用一个类的实例去指定。state是一个是否中断执行过程的标志,如果是false,就中断执行过程。
interface AxiosSugarLifeCycleResult {
state: boolean;
message: string;
}
export default class AxiosSugarLifeCycle {
beforeRequest (conf: AxiosRequestConfig): AxiosSugarLifeCycleResult {
return {
state: true,
message: ''
}
}
beforeResponse (res: AxiosResponse): AxiosSugarLifeCycleResult {
return {
state: true,
message: ''
}
}
}
主流程
主流程就是设置拦截器的过程。拦截器因为需要调用AxiosSugar保存的各种类的实例,所以需要传入它自己和私有的堆栈类给拦截器调用。
首先,设置请求的拦截器。它的执行过程是:
resolve:
-
判断请求是否重复
- 如果是,中断请求执行,直接跳到响应拦截器等待处理。
- 如果否,执行下一步
-
判断请求已经存储
- 如果是,中断请求执行,直接跳到响应拦截器等待处理。
- 如果否,执行下一步
reject:
- 中断请求执行,直接跳到响应拦截器等待处理。
export default function (
sugar: AxiosSugar,
stack: AxiosSugarRequestStack
): void {
const axios = sugar.axios;
const storage = sugar.storage;
const conf = sugar.config;
const lifecycle = sugar.lifecycle;
let error: AxiosSugarError | Error;
axios.interceptors.request.use(config => {
config = normalizeProp(config, conf.prop);
if (stack.contains(config)) {
error = { reason: 'existed' };
return Promise.reject(error);
}
// get custom options
const custom = config[conf.prop];
let isSave = conf.isSave;
if (custom) {
isSave = notUndef(custom.isSave, isSave);
}
if (isSave) {
const storageRes = storage.get(genSymbol(config));
if (storageRes) {
error = { reason: 'saved', data: storageRes };
return Promise.reject(error);
}
}
const cycleRes = lifecycle.beforeRequest(config);
if (!cycleRes.state) {
error = { reason: 'beforeRequestBreak', message: cycleRes.message };
return Promise.reject(error);
}
// send request
stack.push(config);
return Promise.resolve(config);
}, err => {
Promise.reject(err);
});
}
返回是响应拦截器。它的执行过程是:
resolve:
- 将该请求移除栈
-
判断是否存储该响应结果
- 如果是,存储,执行下一步
- 如果否,执行下一步
reject:
- 处理一些自定义的错误(请求拦截器产生)
- 将该请求移除栈
-
是否重传该请求
- 如果是,重新发送请求,执行下一步
- 如果否,执行下一步
export default function (
sugar: AxiosSugar,
stack: AxiosSugarRequestStack
): void {
const axios = sugar.axios;
const storage = sugar.storage;
const conf = sugar.config;
const lifecycle = sugar.lifecycle;
let error: AxiosSugarError | Error;
axios.interceptors.response.use(res => {
const config = res.config;
const resData = res.data;
if (config) {
stack.remove(config);
}
const cycleRes = lifecycle.beforeResponse(res);
if (!cycleRes.state) {
error = { reason: 'beforeResponseBreack', message: cycleRes.message };
return Promise.reject(error);
}
// get custom option
const custom = config.custom;
let isSave;
if (custom) {
isSave = notUndef(custom.isSave, conf.isSave);
}
// generate symbol string
if (isSave) {
const symbol = genSymbol(config);
storage.set(symbol, resData);
}
return Promise.resolve(resData);
}, err => {
// if AxiosSugarError
const reason = err.reason;
if (reason) {
return reason === 'saved' ? Promise.resolve(err.data) : Promise.reject(err);
}
const config = err.config;
// axios error
if (config) {
// remove this request in stack
stack.remove(config);
// get custom options
const custom = config[conf.prop];
let isResend = conf.isResend,
resendDelay = conf.resendDelay,
resendTimes = conf.resendTimes,
curResendTimes = 0;
if (custom) {
isResend = notUndef(custom.isResend, isResend);
resendDelay = notUndef(custom.resendDelay, resendDelay);
resendTimes = notUndef(custom.resendTimes, resendTimes);
curResendTimes = notUndef(custom.curResendTimes, 0);
}
if (isResend) {
if (curResendTimes < resendTimes) {
return new Promise(resolve => {
setTimeout(() => {
if (!custom) {
config.custom = {};
}
config.custom.curResendTimes = ++curResendTimes;
if (isStr(config.data)) {
config.data = JSON.parse(config.data);
}
return resolve(axios.request(config));
}, resendDelay);
});
} else {
error = {reason: 'resendEnd', message: `Can't get a response.`};
return Promise.reject(error);
}
} else {
return Promise.reject(err);
}
}
});
}
具体用法见axios-sugar
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。