虽然对面向对象理解不够深,仍是尝试使用面向对象的方法进行封装。因此,选择使用TypeScript做为编程语言。主要的类关系以下图:
AxiosSugar就是最主要的类,它依赖外部传入axios(或axios的实例),AxiosSugarConfig(配置),AxiosSugarLifeCycle(生命周期/回调函数),AxiosSugarStorage(存储类)。
因为主要使用拦截器实现功能,因此该类只是一个“外部装载”的类,它并不暴露方法,仅仅用于添加拦截器和保存它依赖的类的实例。ios
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类能够实例化,将它设为接口,而后它的子类去实现它。
AxiosSugarStorage是一个接口,它指定三个必须实现的方法set, get, contains
,而后其它的类去实现它。npm
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 } }
为了实现取消重复请求,因此须要一个类来存储每个请求。请求开始时将其入栈,请求完成后将该请求出栈。而后在请求开始时判断是否栈中有相同的请求,就取消这个请求的发送。编程
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); }); } }
为了对一些特定时期进行控制,须要一些回调函数的存在,如今用一个类的实例去指定。state是一个是否中断执行过程的标志,若是是false,就中断执行过程。axios
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:编程语言
判断请求是否重复函数
判断请求已经存储this
reject:spa
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:code
判断是否存储该响应结果orm
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