Axios 如何緩存請求數據?

本文是 Axios 四部曲的最後一篇文章,這篇文章將介紹在 Axios 中如何經過 加強默認適配器 來緩存請求數據。那麼爲何要緩存請求數據呢?這是由於在緩存未失效時,咱們能夠直接使用已緩存的數據,而不需發起請求從服務端獲取數據,這樣不只能夠減小 HTTP 請求並且還能減小等待時間從而提升用戶體驗。javascript

這篇文章。爲了讓你們可以更好地理解後續的內容,咱們先來看一下總體的流程圖:html

上圖中藍色部分的工做流程,就是本文的重點。接下來,阿寶哥將從如何設計緩存開始,帶你們一塊兒來開發緩存請求數據的功能。java

1、如何設計緩存

在計算中,緩存是一個高速數據存儲層,其中存儲了數據子集,且一般是 短暫性 存儲,這樣往後再次請求該數據時,速度要比訪問數據的主存儲位置快。經過緩存,你能夠高效地重用以前檢索或計算的數據。瞭解完緩存的做用以後,咱們來設計緩存的 API:ios

  • get(key):從緩存中獲取指定 key 對應的值;
  • delete(key):從緩存中刪除指定 key 對應的值;
  • clear():清空已緩存的數據;
  • set(key, value, maxAge):保存鍵值對,同時支持設置緩存的最大時間,即 maxAge 單位爲毫秒。

基於上述的緩存 API,咱們能夠實現一個簡單的緩存功能,具體代碼以下所示:git

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-cachegithub

LRU 緩存淘汰算法就是一種經常使用策略。LRU 的全稱是 Least Recently Used,也就是說咱們認爲最近使用過的數據應該是是「有用的」,好久都沒用過的數據應該是無用的,內存滿了就優先刪那些好久沒用過的數據。算法

2、如何加強默認適配器

Axios 引入了適配器,使得它能夠同時支持瀏覽器和 Node.js 環境。對於瀏覽器環境來講,它經過封裝 XMLHttpRequest API 來發送 HTTP 請求,而對於 Node.js 環境來講,它經過封裝 Node.js 內置的 httphttps 模塊來發送 HTTP 請求。npm

在介紹如何加強默認適配器以前,咱們先來回顧一下 Axios 完整請求的流程:json

瞭解完 Axios 完整請求的流程以後,咱們再來看一下 Axios 內置的 xhrAdapter 適配器,它被定義在 lib/adapters/xhr.js 文件中:axios

// 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 適配器的功能。

關注「全棧修仙之路」閱讀阿寶哥原創的 4 本免費電子書(累計下載 3萬+)及 50 幾篇 TS 系列教程。

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 緩存請求數據的功能已經介紹完了。因爲完整的示例代碼內容比較多,阿寶哥就不放具體的代碼了。感興趣的小夥伴,能夠訪問如下地址瀏覽示例代碼。

完整的示例代碼: http://github.crmeb.net/u/defu

3、總結

本文介紹了在 Axios 中如何緩存請求數據及如何設計緩存對象,基於文中定義的 cacheAdapterEnhancer 函數,你能夠輕鬆地擴展緩存的功能。至此 Axios 四部曲已經所有更新完成了,如下是其餘文章的連接,感興趣的小夥伴能夠了解一下。寫得很差的地方,請多多包涵。

若是你以爲這篇文章對你有點用的話,麻煩請給咱們的開源項目點點star: http://github.crmeb.net/u/defu 不勝感激 !
來自 「開源世界 」 ,連接: https://ym.baisou.ltd/post/731.html ,如需轉載,請註明出處,不然將追究法律責任。 ​​​​​​
 
相關文章
相關標籤/搜索