Axios 如何緩存請求數(shù)據(jù)?
本文轉(zhuǎn)載自微信公眾號「全棧修仙之路」,作者阿寶哥。轉(zhuǎn)載本文請聯(lián)系全棧修仙之路公眾號。
在 Axios 如何取消重復(fù)請求? 這篇文章中,阿寶哥介紹了在 Axios 中如何取消重復(fù)請求及 CancelToken 的工作原理。本文將介紹在 Axios 中如何通過增強默認適配器來緩存請求數(shù)據(jù)。那么為什么要緩存請求數(shù)據(jù)呢?這是因為在緩存未失效時,我們可以直接使用已緩存的數(shù)據(jù),而不需發(fā)起請求從服務(wù)端獲取數(shù)據(jù),這樣不僅可以減少 HTTP 請求而且還能減少等待時間從而提高用戶體驗。
因為本文將使用 Axios 提供的默認適配器來實現(xiàn)緩存請求數(shù)據(jù)的功能,所以如果你對 Axios 適配器還不熟悉的話,建議先閱讀 77.9K 的 Axios 項目有哪些值得借鑒的地方 這篇文章。為了讓大家能夠更好地理解后續(xù)的內(nèi)容,我們先來看一下整體的流程圖:
上圖中藍色部分的工作流程,就是本文的重點。接下來,阿寶哥將從如何設(shè)計緩存開始,帶大家一起來開發(fā)緩存請求數(shù)據(jù)的功能。
一、如何設(shè)計緩存
在計算中,緩存是一個高速數(shù)據(jù)存儲層,其中存儲了數(shù)據(jù)子集,且通常是 短暫性 存儲,這樣日后再次請求該數(shù)據(jù)時,速度要比訪問數(shù)據(jù)的主存儲位置快。通過緩存,你可以高效地重用之前檢索或計算的數(shù)據(jù)。了解完緩存的作用之后,我們來設(shè)計緩存的 API:
- get(key):從緩存中獲取指定 key 對應(yīng)的值;
- delete(key):從緩存中刪除指定 key 對應(yīng)的值;
- clear():清空已緩存的數(shù)據(jù);
- set(key, value, maxAge):保存鍵值對,同時支持設(shè)置緩存的最大時間,即 maxAge 單位為毫秒。
基于上述的緩存 API,我們可以實現(xiàn)一個簡單的緩存功能,具體代碼如下所示:
- const MemoryCache = {
- data: {},
- set(key, value, maxAge) { // 保存數(shù)據(jù)
- this.data[key] = {
- maxAge: maxAge || 0,
- value,
- now: Date.now(),
- };
- },
- get(key) { // 從緩存中獲取指定 key 對應(yīng)的值。
- const cachedItem = this.data[key];
- if (!cachedItem) return null;
- const isExpired = Date.now() - cachedItem.now > cachedItem.maxAge;
- isExpired && this.delete(key);
- return isExpired ? null : cachedItem.value;
- },
- delete(key) { // 從緩存中刪除指定 key 對應(yīng)的值。
- return delete this.data[key];
- },
- clear() { // 清空已緩存的數(shù)據(jù)。
- this.data = {};
- },
- };
其實除了自定義緩存對象之外,你也可以使用成熟的第三方庫,比如 lru-cache。
LRU 緩存淘汰算法就是一種常用策略。LRU 的全稱是 Least Recently Used,也就是說我們認為最近使用過的數(shù)據(jù)應(yīng)該是是「有用的」,很久都沒用過的數(shù)據(jù)應(yīng)該是無用的,內(nèi)存滿了就優(yōu)先刪那些很久沒用過的數(shù)據(jù)。
二、如何增強默認適配器
Axios 引入了適配器,使得它可以同時支持瀏覽器和 Node.js 環(huán)境。對于瀏覽器環(huán)境來說,它通過封裝 XMLHttpRequest API 來發(fā)送 HTTP 請求,而對于 Node.js 環(huán)境來說,它通過封裝 Node.js 內(nèi)置的 http 和 https 模塊來發(fā)送 HTTP 請求。
在介紹如何增強默認適配器之前,我們先來回顧一下 Axios 完整請求的流程:
了解完 Axios 完整請求的流程之后,我們再來看一下 Axios 內(nèi)置的 xhrAdapter 適配器,它被定義在 lib/adapters/xhr.js 文件中:
- // lib/adapters/xhr.js
- module.exports = function xhrAdapter(config) {
- return new Promise(function dispatchXhrRequest(resolve, reject) {
- var requestData = config.data;
- var requestHeaders = config.headers;
- var request = new XMLHttpRequest();
- // 省略大部分代碼
- var fullPath = buildFullPath(config.baseURL, config.url);
- request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
- // Set the request timeout in MS
- request.timeout = config.timeout;
- // Listen for ready state
- request.onreadystatechange = function handleLoad() { ... }
- // Send the request
- request.send(requestData);
- });
- };
很明顯 xhrAdapter 適配器是一個函數(shù)對象,它接收一個 config 參數(shù)并返回一個 Promise 對象。而在 xhrAdapter 適配器內(nèi)部,最終會使用 XMLHttpRequest API 來發(fā)送 HTTP 請求。為了實現(xiàn)緩存請求數(shù)據(jù)的功能,我們就可以考慮通過高階函數(shù)來增強 xhrAdapter 適配器的功能。
2.1 定義輔助函數(shù)
2.1.1 定義 generateReqKey 函數(shù)
在增強 xhrAdapter 適配器之前,我們先來定義一個 generateReqKey 函數(shù),該函數(shù)用于根據(jù)當(dāng)前請求的信息,生成請求 Key;
- function generateReqKey(config) {
- const { method, url, params, data } = config;
- return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
- }
通過 generateReqKey 函數(shù)生成的請求 key,將作為緩存項的 key,而對應(yīng)的 value 就是默認 xhrAdapter 適配器返回的 Promise 對象。
2.1.2 定義 isCacheLike 函數(shù)
isCacheLike 函數(shù)用于判斷傳入的 cache 參數(shù)是否實現(xiàn)了前面定義的 Cache API,利用該函數(shù),我們允許用戶為每個請求自定義 Cache 對象。
- function isCacheLike(cache) {
- return !!(cache.set && cache.get && cache.delete && cache.clear
- && typeof cache.get === 'function' && typeof cache.set === 'function'
- && typeof cache.delete === 'function' && typeof cache.clear === 'function'
- );
- }
2.2 定義 cacheAdapterEnhancer 函數(shù)
為了讓用戶能夠更靈活地控制數(shù)據(jù)緩存的功能,我們定義了一個 cacheAdapterEnhancer 函數(shù),該函數(shù)支持兩個參數(shù):
adapter:預(yù)增強的 Axios 適配器對象;
options:緩存配置對象,該對象支持 4 個屬性,分別用于配置不同的功能:
maxAge:全局設(shè)置緩存的最大時間;
enabledByDefault:是否啟用緩存,默認為 true;
cacheFlag:緩存標(biāo)志,用于配置請求 config 對象上的緩存屬性;
defaultCache:用于設(shè)置使用的緩存對象。
了解完 cacheAdapterEnhancer 函數(shù)的參數(shù)之后,我們來看一下該函數(shù)的具體實現(xiàn):
- function cacheAdapterEnhancer(adapter, options) {
- const { maxAge, enabledByDefault = true,
- cacheFlag = "cache", defaultCache = MemoryCache,
- } = options;
- return (config) => {
- const { url, method, params, forceUpdate } = config;
- let useCache = config[cacheFlag] !== undefined && config[cacheFlag] !== null
- ? config[cacheFlag]
- : enabledByDefault;
- if (method === "get" && useCache) {
- const cache = isCacheLike(useCache) ? useCache : defaultCache;
- let requestKey = generateReqKey(config); // 生成請求Key
- let responsePromise = cache.get(requestKey); // 從緩存中獲取請求key對應(yīng)的響應(yīng)對象
- if (!responsePromise || forceUpdate) { // 緩存未命中/失效或強制更新時,則重新請求數(shù)據(jù)
- responsePromise = (async () => {
- try {
- return await adapter(config); // 使用默認的xhrAdapter發(fā)送請求
- } catch (reason) {
- cache.delete(requestKey);
- throw reason;
- }
- })();
- cache.set(requestKey, responsePromise, maxAge); // 保存請求返回的響應(yīng)對象
- return responsePromise; // 返回已保存的響應(yīng)對象
- }
- return responsePromise;
- }
- return adapter(config); // 使用默認的xhrAdapter發(fā)送請求
- };
- }
以上的代碼并不會復(fù)雜,核心的處理邏輯如下圖所示:
2.3 使用 cacheAdapterEnhancer 函數(shù)
2.3.1 創(chuàng)建 Axios 對象并配置 adapter 選項
- const http = axios.create({
- baseURL: "https://jsonplaceholder.typicode.com",
- adapter: cacheAdapterEnhancer(axios.defaults.adapter, {
- enabledByDefault: false, // 默認禁用緩存
- maxAge: 5000, // 緩存時間為5s
- }),
- });
2.3.2 使用 http 對象發(fā)送請求
- // 使用緩存
- async function requestWithCache() {
- const response = await http.get("/todos/1", { cache: true });
- console.dir(response);
- }
- // 不使用緩存
- async function requestWithoutCache() {
- const response = await http.get("/todos/1", { cache: false });
- console.dir(response);
- }
其實 cache 屬性除了支持布爾值之外,我們可以配置實現(xiàn) Cache API 的緩存對象,具體的使用示例如下所示:
- const customCache = { get() {/*...*/}, set() {/*...*/}, delete() {/*...*/}, clear() {/*...*/}};
- async function requestForceUpdate() {
- const response = await http.get("/todos/1", {
- cache: customCache,
- forceUpdate: true,
- });
- console.dir(response);
- }
好了,如何通過增強 xhrAdapter 適配器來實現(xiàn) Axios 緩存請求數(shù)據(jù)的功能已經(jīng)介紹完了。由于完整的示例代碼內(nèi)容比較多,阿寶哥就不放具體的代碼了。感興趣的小伙伴,可以訪問以下地址瀏覽示例代碼。
完整的示例代碼:https://gist.github.com/semlinker/b8a7bd5a0a16c2d04011c2c4a8167fbd
三、總結(jié)
本文介紹了在 Axios 中如何緩存請求數(shù)據(jù)及如何設(shè)計緩存對象,基于文中定義的 cacheAdapterEnhancer 函數(shù),你可以輕松地擴展緩存的功能。在后續(xù)的文章中,阿寶哥將會介紹在 Axios 中如何實現(xiàn)請求重試功能,感興趣的小伙伴不要錯過喲。