前端持久化快取最佳化

發表於2023-09-26

快取是提升 web 應用程式有效方法之一,尤其是使用者受限於網速的情況下。提升系統的響應能力,降低網路的消耗。當然,內容越接近於使用者,則快取的速度就會越快,快取的有效性則會越高。

之前個人寫過 前端 api 請求快取方案。介紹的了記憶體中的快取以及過期邏輯。後續也寫過 手寫一個前端儲存工具庫,該工具利用了介面卡處理了不同的儲存介質(記憶體,IndexedDB, localStorage 等)。

不過,在某些特定場景下快取還需要最佳化,例如:使用者需要在登入或者填寫表單時需要透過某些介面獲取必要資料,而這些介面是由第三方平臺提供的。這些介面可能會出現錯誤或超時的情況。如果當前資料有很強實時性,開發者就必須重試或者聯絡第三方平臺來處理對應的錯誤。如果資料的實時性不強,當前就可以使用本地快取。

一般來說,當獲取時效性快取時候,我們會檢查並刪除當前資料。程式碼簡寫如下所示:

// 快取對應的的模組以及功能
const EXTRA_INFO_CACHE_KEY = 'xxx.xxx.xxx';
// 快取時長為 7 天
const CACHE_TIME =  7 * 24 * 60 * 60 * 1000;

const getCachedExtraInfo = () => {
  const cacheStr = localStorage.getItem(`${EXTRA_INFO_CACHE_KEY}.${userId}`);

  if (!cacheStr) {
    return null;
  }

  let cache = null;
  try {
    cache = JSON.parse(cacheStr);
  } catch () {
    return null;
  }

  if (!cache) {
    return null;
  }

  // 快取過期了,直接返回 null
  if ((cache.expiredTime ?? 0) < new Date().getTime()) {
    return null;
  }

  return cache.data;
}

const getExtraInfo = () => {
  const cacheData = getCachedExtraInfo();
  if (cacheData) {
    return Promise.resolve(cacheData);
  }

  return getExtraInfoApi().then(res => {
    localStorage.setItem(`${EXTRA_INFO_CACHE_KEY}.${userId}`, {
      data: res,
      expiredTime: (new Data()).getTime() + CACHE_TIME,
    });
    return res;
  });
}

如果這時候介面出現了訪問錯誤問題,很多資料到期的使用者就無法正常使用功能了,這時候新增重試功能可能會解決某些錯誤。這時候我們先不考慮重試的邏輯。

考慮到絕大部份使用者對應資料不會進行修改的情況下,對應程式碼就可以不進行資料刪除。而是返回超時標記。

const EXTRA_INFO_CACHE_KEY = 'xxx.xxx.xxx';
const CACHE_TIME =  7 * 24 * 60 * 60 * 1000;

const getCachedExtraInfo = () => {
  const cacheStr = localStorage.getItem(`${EXTRA_INFO_CACHE_KEY}.${userId}`);

  if (!cacheStr) {
    return null;
  }

  let cache = null;
  try {
    cache = JSON.parse(cacheStr)
  } catch () {
    return null;
  }

  if (!cache) {
    return null;
  }

  if ((cache.expiredTime ?? 0) < new Date().getTime()) {
    return {
      data: cache.data,
      // 資料已經超時了
      isOverTime: true,
    };
  }

  return  return {
    data: cache.data,
    // 資料沒有超時
    isOverTime: false,
  };
}

const getExtraInfo = () => {
  const cacheInfo = getCachedExtraInfo();
  // 資料沒有超時才返回對應資料
  if (cacheInfo && !cacheInfo.isOverTime) {
      return Promise.resolve(cacheInfo.data);
  }

  return getExtraInfoApi().then(res => {
    localStorage.setItem(`${EXTRA_INFO_CACHE_KEY}.${userId}`, {
      data: res,
      expiredTime: (new Data()).getTime() + CACHE_TIME,
    });
    return res;
  }).catch(err => {
    // 有資料,才返回,否則繼續丟擲錯誤
    if (cacheInfo) {
      return cacheInfo.data;
    }
    throw err;
  })
}

這樣的話,我們可以保證絕大多數使用者是可以繼續正常使用的。但如果對應的介面不穩定,會讓使用者等待很長時間才能繼續使用。

這時候開發者可以考慮完全拋棄非同步程式碼,同時減少快取時間。

const EXTRA_INFO_CACHE_KEY = 'xxx.xxx.xxx';
// 將快取時效減少為 5 天
const CACHE_TIME =  5 * 24 * 60 * 60 * 1000;

const getCachedExtraInfo = () => {
  const cacheStr = localStorage.getItem(`${EXTRA_INFO_CACHE_KEY}.${userId}`);

  if (!cacheStr) {
    return null;
  }

  let cache = null;
  try {
    cache = JSON.parse(cacheStr)
  } catch () {
    return null;
  }

  if (!cache) {
    return null;
  }

  if ((cache.expiredTime ?? 0) < new Date().getTime()) {
    return {
      data: cache.data,
      isOverTime: true,
    };
  }

  return  return {
    data: cache.data,
    isOverTime: false,
  };
}

const getExtraInfo = () => {
  const cacheInfo = getCachedExtraInfo();
  // 如果超時了,就去獲取,下一次再使用即可
  if (cacheInfo.isOverTime) {
      getExtraInfoApi().then(res => {
        localStorage.setItem(`${EXTRA_INFO_CACHE_KEY}.${userId}`, {
          data: res,
          expiredTime: (new Data()).getTime() + CACHE_TIME,
        })
      })
  }
  return cacheInfo.data
}

參考檔案

前端 api 請求快取方案

手寫一個前端儲存工具庫

鼓勵一下

如果你覺得這篇文章不錯,希望可以給與我一些鼓勵,在我的 github 部落格下幫忙 star 一下。

部落格地址

相關文章