雖然對面向對象理解不夠深,仍是嘗試使用面向對象的方法進行封裝。因此,選擇使用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