Axios 如何緩存請求數據?
本文將介紹在 Axios 中如何通過增強默認適配器來緩存請求數據。那麼爲什麼要緩存請求數據呢?這是因爲在緩存未失效時,我們可以直接使用已緩存的數據,而不需發起請求從服務端獲取數據,這樣不僅可以減少 HTTP 請求而且還能減少等待時間從而提高用戶體驗。
因爲本文將使用 Axios 提供的默認適配器來實現緩存請求數據的功能,所以如果你對 Axios 適配器還不熟悉的話,建議先閱讀 77.9K 的 Axios 項目有哪些值得借鑑的地方 這篇文章。爲了讓大家能夠更好地理解後續的內容,我們先來看一下整體的流程圖:
上圖中藍色部分的工作流程,就是本文的重點。接下來,阿寶哥將從如何設計緩存開始,帶大家一起來開發緩存請求數據的功能。
一、如何設計緩存
在計算中,緩存是一個高速數據存儲層,其中存儲了數據子集,且通常是 短暫性 存儲,這樣日後再次請求該數據時,速度要比訪問數據的主存儲位置快。通過緩存,你可以高效地重用之前檢索或計算的數據。瞭解完緩存的作用之後,我們來設計緩存的 API:
-
get(key):從緩存中獲取指定
key
對應的值; -
delete(key):從緩存中刪除指定
key
對應的值; -
clear():清空已緩存的數據;
-
set(key, value, maxAge):保存鍵值對,同時支持設置緩存的最大時間,即
maxAge
單位爲毫秒。
基於上述的緩存 API,我們可以實現一個簡單的緩存功能,具體代碼如下所示:
const MemoryCache = {
data: {},
set(key, value, maxAge) { // 保存數據
this.data[key] = {
maxAge: maxAge || 0,
value,
now: Date.now(),
};
},
get(key) { // 從緩存中獲取指定 key 對應的值。
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 對應的值。
return delete this.data[key];
},
clear() { // 清空已緩存的數據。
this.data = {};
},
};
其實除了自定義緩存對象之外,你也可以使用成熟的第三方庫,比如 lru-cache。
LRU 緩存淘汰算法就是一種常用策略。LRU 的全稱是 Least Recently Used,也就是說我們認爲最近使用過的數據應該是是「有用的」,很久都沒用過的數據應該是無用的,內存滿了就優先刪那些很久沒用過的數據。
二、如何增強默認適配器
Axios 引入了適配器,使得它可以同時支持瀏覽器和 Node.js 環境。對於瀏覽器環境來說,它通過封裝 XMLHttpRequest
API 來發送 HTTP 請求,而對於 Node.js 環境來說,它通過封裝 Node.js 內置的 http
和 https
模塊來發送 HTTP 請求。
在介紹如何增強默認適配器之前,我們先來回顧一下 Axios 完整請求的流程:
瞭解完 Axios 完整請求的流程之後,我們再來看一下 Axios 內置的 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
適配器是一個函數對象,它接收一個 config
參數並返回一個 Promise
對象。而在 xhrAdapter
適配器內部,最終會使用 XMLHttpRequest API 來發送 HTTP 請求。爲了實現緩存請求數據的功能,我們就可以考慮通過高階函數來增強 xhrAdapter
適配器的功能。
2.1 定義輔助函數
2.1.1 定義 generateReqKey 函數
在增強 xhrAdapter
適配器之前,我們先來定義一個 generateReqKey
函數,該函數用於根據當前請求的信息,生成請求 Key;
function generateReqKey(config) {
const { method, url, params, data } = config;
return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&");
}
通過 generateReqKey
函數生成的請求 key,將作爲緩存項的 key,而對應的 value 就是默認 xhrAdapter
適配器返回的 Promise 對象。
2.1.2 定義 isCacheLike 函數
isCacheLike
函數用於判斷傳入的 cache 參數是否實現了前面定義的 Cache API,利用該函數,我們允許用戶爲每個請求自定義 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 函數
爲了讓用戶能夠更靈活地控制數據緩存的功能,我們定義了一個 cacheAdapterEnhancer
函數,該函數支持兩個參數:
-
adapter:預增強的 Axios 適配器對象;
-
options:緩存配置對象,該對象支持 4 個屬性,分別用於配置不同的功能:
-
maxAge:全局設置緩存的最大時間;
-
enabledByDefault:是否啓用緩存,默認爲 true;
-
cacheFlag:緩存標誌,用於配置請求 config 對象上的緩存屬性;
-
defaultCache:用於設置使用的緩存對象。
瞭解完 cacheAdapterEnhancer
函數的參數之後,我們來看一下該函數的具體實現:
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對應的響應對象
if (!responsePromise || forceUpdate) { // 緩存未命中/失效或強制更新時,則重新請求數據
responsePromise = (async () => {
try {
return await adapter(config); // 使用默認的xhrAdapter發送請求
} catch (reason) {
cache.delete(requestKey);
throw reason;
}
})();
cache.set(requestKey, responsePromise, maxAge); // 保存請求返回的響應對象
return responsePromise; // 返回已保存的響應對象
}
return responsePromise;
}
return adapter(config); // 使用默認的xhrAdapter發送請求
};
}
以上的代碼並不會複雜,核心的處理邏輯如下圖所示:
2.3 使用 cacheAdapterEnhancer 函數
2.3.1 創建 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 對象發送請求
// 使用緩存
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 屬性除了支持布爾值之外,我們可以配置實現 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
適配器來實現 Axios 緩存請求數據的功能已經介紹完了。由於完整的示例代碼內容比較多,阿寶哥就不放具體的代碼了。感興趣的小夥伴,可以訪問以下地址瀏覽示例代碼。
完整的示例代碼:https://gist.github.com/semlinker/b8a7bd5a0a16c2d04011c2c4a8167fbd
三、總結
本文介紹了在 Axios 中如何緩存請求數據及如何設計緩存對象,基於文中定義的 cacheAdapterEnhancer
函數,你可以輕鬆地擴展緩存的功能。在後續的文章中,阿寶哥將會介紹在 Axios 中如何實現請求重試功能,感興趣的小夥伴不要錯過喲。另外,如果你對 Axios 如何取消重複請求感興趣,可以閱讀 Axios 如何取消重複請求? 這篇文章。
四、參考資源
-
Github - axios-extensions
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/NfyxtWUzjHh6ucXvBF9B4Q