axios性能優(yōu)化:防止重復(fù)請(qǐng)求
網(wǎng)站性能優(yōu)化是一系列技術(shù)和策略的應(yīng)用,提高網(wǎng)站的加載速度、響應(yīng)時(shí)間和整體性能,以提供更好的用戶體驗(yàn)和增強(qiáng)網(wǎng)站的競爭力,比較直接的方式就是減少http請(qǐng)求數(shù)量,過濾掉無效請(qǐng)求。
也是本篇文章的主題。
開始
本篇將基于axios開源庫,對(duì)http請(qǐng)求進(jìn)行封裝,包含請(qǐng)求的緩存、重復(fù)請(qǐng)求的過濾兩個(gè)小優(yōu)化。
第一步
首先建立一個(gè) http-helper.js 文件,里面將基于axios進(jìn)行上述相關(guān)功能的封裝
首先里面的內(nèi)容大概是這樣的:
import axios from 'axios'; const http = axios.create(); export default http
上述就是簡單地導(dǎo)出了一個(gè)axios實(shí)例,供項(xiàng)目使用
增加請(qǐng)求緩存功能
那么有了緩存功能,就要對(duì)緩存命中進(jìn)行定義,我定義的緩存命中是指:http請(qǐng)求的url相同、請(qǐng)求參數(shù)相同、請(qǐng)求類型相同,以上三者都相同的情況下,就視為緩存允許命中,最后根據(jù)緩存過期時(shí)間,判斷是否獲取最新數(shù)據(jù),還是從緩存中取。
下面理一下流程:
-
發(fā)起請(qǐng)求,設(shè)置請(qǐng)求是否緩存,緩存多長時(shí)間
-
axios請(qǐng)求攔截,判斷該請(qǐng)求是否設(shè)置緩存,是?則判斷是否緩存命中、是否過期,否?則繼續(xù)發(fā)起請(qǐng)求
-
axios響應(yīng)攔截,判斷該請(qǐng)求結(jié)果是否緩存,是?則緩存數(shù)據(jù),并設(shè)置key值、過期時(shí)間
針對(duì)上面的流程,需要有幾點(diǎn)確認(rèn)一下:
當(dāng)緩存命中時(shí),如何終止請(qǐng)求
-
axios中,可以為每一個(gè)請(qǐng)求設(shè)置一個(gè)cancleToken,當(dāng)調(diào)用請(qǐng)求取消方法的時(shí)候,則請(qǐng)求終止,并將終止的消息通過reject回傳給請(qǐng)求方法。
-
當(dāng)緩存命中時(shí),并將緩存的數(shù)據(jù)通過 resolve() 返回給請(qǐng)求方法,而不是在 reject 中獲取緩存數(shù)據(jù)
那么具體的代碼可以是這樣的:
// http-helper.js import axios from 'axios'; const http = axios.create(); http.interceptors.request.use((config) => { /** * 為每一次請(qǐng)求生成一個(gè)cancleToken */ const source = axios.CancelToken.source(); config.cancelToken = source.token; /** * 嘗試獲取緩存數(shù)據(jù) */ const data = storage.get(cryptoHelper.encrypt( config.url + JSON.stringify(config.data) + (config.method || ''), )); /** * 判斷緩存是否命中,是否未過期 */ if (data && (Date.now() <= data.exppries)) { console.log(`接口:${config.url} 緩存命中 -- ${Date.now()} -- ${data.exppries}`); /** * 將緩存數(shù)據(jù)通過cancle方法回傳給請(qǐng)求方法 */ source.cancel(JSON.stringify({ type: CANCELTTYPE.CACHE, data: data.data, })); } return config; }); http.interceptors.response.use((res) => { if (res.data && res.data.type === 0) { if (res.config.data) { /** * 獲取請(qǐng)求體參數(shù) */ const dataParse = JSON.parse(res.config.data); if (dataParse.cache) { if (!dataParse.cacheTime) { dataParse.cacheTime = 1000 * 60 * 3; } /** * 加密 * 緩存 */ storage.set(cryptoHelper.encrypt(res.config.url + res.config.data + (res.config.method || '')), { data: res.data.data, // 響應(yīng)體數(shù)據(jù) exppries: Date.now() + dataParse.cacheTime, // 設(shè)置過期時(shí)間 }); console.log(`接口:${res.config.url} 設(shè)置緩存,緩存時(shí)間: ${dataParse.cacheTime}`); } } return res.data.data; } else { return Promise.reject('接口報(bào)錯(cuò)了!'); } }); /** * 封裝 get、post 請(qǐng)求 * 集成接口緩存過期機(jī)制 * 緩存過期將重新請(qǐng)求獲取最新數(shù)據(jù),并更新緩存 * 數(shù)據(jù)存儲(chǔ)在localstorage * { * cache: true * cacheTime: 1000 * 60 * 3 -- 默認(rèn)緩存3分鐘 * } */ const httpHelper = { get(url, params) { return new Promise((resolve, reject) => { http.get(url, params).then(async (res) => { resolve(res); }).catch((error) => { if (axios.isCancel(error)) { const cancle = JSON.parse(error.message); if (cancle.type === CANCELTTYPE.REPEAT) { return resolve([]); } else { return resolve(cancle.data); } } else { return reject(error); } }); }); }, post(url: string, params: any) { return new Promise((resolve, reject) => { http.post(url, params).then(async (res) => { resolve(res); }).catch((error: AxiosError) => { if (axios.isCancel(error)) { const cancle = JSON.parse(error.message); if (cancle.type === CANCELTTYPE.REPEAT) { return resolve(null); } else { return resolve(cancle.data); } } else { return reject(error); } }); }); }, }; export default httpHelper
上面代碼中,有些東西沒有解釋到:
1. 其中storage是自己封裝的緩存數(shù)據(jù)類,可以有.get、.set等方法,cryptoHelper是封裝的MD5加密庫,主要是通過MD5加密請(qǐng)求url、請(qǐng)求數(shù)據(jù)、請(qǐng)求類型等拼接的字符串,通過加密后的key來獲取緩存中的數(shù)據(jù)(因?yàn)槠唇雍蟮淖址L,通過MD5加密一下,會(huì)短很多)
2. 為什么要單獨(dú)封裝一個(gè) httpHelper,因?yàn)閍xios.CancelToken.source().cancle(***)中的信息,只能在reject中取到,為了緩存命中時(shí),仍然能在then中獲取到正確的數(shù)據(jù),則需要單獨(dú)處理一下這個(gè)情況。
增加重復(fù)請(qǐng)求過濾功能
規(guī)則: 以最新的請(qǐng)求為主,即最新的重復(fù)請(qǐng)求,會(huì)將之前的重復(fù)請(qǐng)求中斷掉
大概流程如下:
1. 發(fā)起請(qǐng)求
2. axios請(qǐng)求攔截,判斷請(qǐng)求列表數(shù)組中,是否存在相同的請(qǐng)求,是?終止之前所有重復(fù)請(qǐng)求,否?將當(dāng)次請(qǐng)求添加進(jìn)請(qǐng)求數(shù)組中,最終都繼續(xù)會(huì)請(qǐng)求
3. axios響應(yīng)攔截器,將當(dāng)次請(qǐng)求從請(qǐng)求數(shù)組中刪除
具體代碼如下:
// http-helper.js import axios from 'axios'; const http = axios.create(); const pendingRequests = []; http.interceptors.request.use((config) => { /** * 為每一次請(qǐng)求生成一個(gè)cancleToken */ const source = axios.CancelToken.source(); config.cancelToken = source.token; // .... 省略部分代碼 /** * 重復(fù)請(qǐng)求判斷 * 同url,同請(qǐng)求類型判定為重復(fù)請(qǐng)求 * 以最新的請(qǐng)求為準(zhǔn) */ const md5Key = cryptoHelper.encrypt(config.url + (config.method || '')); /** * 將之前的重復(fù)且未完成的請(qǐng)求全部取消 */ const hits = pendingRequests.filter((item) => item.md5Key === md5Key); if (hits.length > 0) { hits.forEach((item) => item.source.cancel(JSON.stringify({ type: CANCELTTYPE.REPEAT, data: '重復(fù)請(qǐng)求,以取消', }))); } /** * 將當(dāng)前請(qǐng)求添加進(jìn)請(qǐng)求對(duì)列中 */ pendingRequests.push({ md5Key, source, }); return config; }); http.interceptors.response.use((res) => { /** * 不論請(qǐng)求是否成功, * 將本次完成的請(qǐng)求從請(qǐng)求隊(duì)列中移除 */ // 以同樣的加密方式(MD5)獲取加密字符串 const md5Key = cryptoHelper.encrypt(res.config.url + (res.config.method || '')); const index = pendingRequests.findIndex((item) => item.md5Key === md5Key); if (index > -1) { pendingRequests.splice(index, 1); } // .... 省略部分代碼 }); // .... 省略部分代碼
其實(shí)邏輯很簡單,通過一個(gè)數(shù)組去維護(hù)請(qǐng)求列表即可
最終成果物
是用ts寫的,需要使用可以改成 js
由于緩存和終止重復(fù)請(qǐng)求,都需要用到source.cancle,因此需要一個(gè)type值,區(qū)分是緩存命中終止,還是重復(fù)請(qǐng)求終止,代碼中是CANCELTTYPE常量。
**http-helper.ts ** import axios, {CancelTokenSource, AxiosResponse, AxiosRequestConfig, AxiosError} from 'axios'; import Storage from './storage-helper'; import CryptoHelper from './cryptoJs-helper'; const CANCELTTYPE = { CACHE: 1, REPEAT: 2, }; interface ICancel { data: any; type: number; } interface Request { md5Key: string; source: CancelTokenSource; } const pendingRequests: Request[] = []; const http = axios.create(); const storage = new Storage(); const cryptoHelper = new CryptoHelper('cacheKey'); http.interceptors.request.use((config: AxiosRequestConfig) => { /** * 為每一次請(qǐng)求生成一個(gè)cancleToken */ const source = axios.CancelToken.source(); config.cancelToken = source.token; /** * 緩存命中判斷 * 成功則取消當(dāng)次請(qǐng)求 */ const data = storage.get(cryptoHelper.encrypt( config.url + JSON.stringify(config.data) + (config.method || ''), )); if (data && (Date.now() <= data.exppries)) { console.log(`接口:${config.url} 緩存命中 -- ${Date.now()} -- ${data.exppries}`); source.cancel(JSON.stringify({ type: CANCELTTYPE.CACHE, data: data.data, })); } /** * 重復(fù)請(qǐng)求判斷 * 同url,同請(qǐng)求類型判定為重復(fù)請(qǐng)求 * 以最新的請(qǐng)求為準(zhǔn) */ const md5Key = cryptoHelper.encrypt(config.url + (config.method || '')); /** * 將之前的重復(fù)且未完成的請(qǐng)求全部取消 */ const hits = pendingRequests.filter((item) => item.md5Key === md5Key); if (hits.length > 0) { hits.forEach((item) => item.source.cancel(JSON.stringify({ type: CANCELTTYPE.REPEAT, data: '重復(fù)請(qǐng)求,以取消', }))); } /** * 將當(dāng)前請(qǐng)求添加進(jìn)請(qǐng)求對(duì)列中 */ pendingRequests.push({ md5Key, source, }); return config; }); http.interceptors.response.use((res: AxiosResponse) => { /** * 不論請(qǐng)求是否成功, * 將本次完成的請(qǐng)求從請(qǐng)求隊(duì)列中移除 */ // 以同樣的加密方式(MD5)獲取加密字符串 const md5Key = cryptoHelper.encrypt(res.config.url + (res.config.method || '')); const index = pendingRequests.findIndex((item) => item.md5Key === md5Key); if (index > -1) { pendingRequests.splice(index, 1); } if (res.data && res.data.type === 0) { if (res.config.data) { const dataParse = JSON.parse(res.config.data); if (dataParse.cache) { if (!dataParse.cacheTime) { dataParse.cacheTime = 1000 * 60 * 3; } storage.set(cryptoHelper.encrypt(res.config.url + res.config.data + (res.config.method || '')), { data: res.data.data, exppries: Date.now() + dataParse.cacheTime, }); console.log(`接口:${res.config.url} 設(shè)置緩存,緩存時(shí)間: ${dataParse.cacheTime}`); } } return res.data.data; } else { return Promise.reject('接口報(bào)錯(cuò)了!'); } }); /** * 封裝 get、post 請(qǐng)求 * 集成接口緩存過期機(jī)制 * 緩存過期將重新請(qǐng)求獲取最新數(shù)據(jù),并更新緩存 * 數(shù)據(jù)存儲(chǔ)在localstorage * { * cache: true * cacheTime: 1000 * 60 * 3 -- 默認(rèn)緩存3分鐘 * } */ const httpHelper = { get(url: string, params: any) { return new Promise((resolve, reject) => { http.get(url, params).then(async (res: AxiosResponse) => { resolve(res); }).catch((error: AxiosError) => { if (axios.isCancel(error)) { const cancle: ICancel = JSON.parse(error.message); if (cancle.type === CANCELTTYPE.REPEAT) { return resolve([]); } else { return resolve(cancle.data); } } else { return reject(error); } }); }); }, post(url: string, params: any) { return new Promise((resolve, reject) => { http.post(url, params).then(async (res: AxiosResponse) => { resolve(res); }).catch((error: AxiosError) => { if (axios.isCancel(error)) { const cancle: ICancel = JSON.parse(error.message); if (cancle.type === CANCELTTYPE.REPEAT) { return resolve(null); } else { return resolve(cancle.data); } } else { return reject(error); } }); }); }, }; export default httpHelper; cryptoJs-helper.ts import cryptoJs from 'crypto-js'; class CryptoHelper { public key: string; constructor(key: string) { /** * 如需秘鑰,可以在實(shí)例化時(shí)傳入 */ this.key = key; } /** * 加密 * @param word */ public encrypt(word: string | undefined): string { if (!word) { return ''; } const encrypted = cryptoJs.MD5(word); return encrypted.toString(); } } export default CryptoHelper; storage-helper.ts class Storage { public get(key: string | undefined) { if (!key) { return; } const text = localStorage.getItem(key); try { if (text) { return JSON.parse(text); } else { localStorage.removeItem(key); return null; } } catch { localStorage.removeItem(key); return null; } } public set(key: string | undefined, data: any) { if (!key) { return; } localStorage.setItem(key, JSON.stringify(data)); } public remove(key: string | undefined) { if (!key) { return; } localStorage.removeItem(key); } } export default Storage;
掃描二維碼推送至手機(jī)訪問。
版權(quán)聲明:本文由星星博客發(fā)布,如需轉(zhuǎn)載請(qǐng)注明出處。