前端緩存

前端緩存

提示
這里講的前端緩存是指前端對接口數(shù)據(jù)的緩存處理累澡,而不是通過 HTTP(s)緩存

前言

通常會在項目中有這么些情況發(fā)生涉馅,比如每次頁面切換的時候都會請求接口,如果頻繁切換阱穗,也就會導致接口頻繁的請求俘种,而且在數(shù)據(jù)基本沒有什么變動的情況下,這樣的做法明顯是浪費網(wǎng)絡資源的篇亭。所以我們出于考慮缠捌,要實現(xiàn)接口的緩存,避免頻繁的去請求接口译蒂。如果后端同學不給于幫助的話曼月。。柔昼。那我們就進入今天的主題--前端緩存哑芹。(當然,能 http 緩存就 http 緩存最好了~)

怎么做?

思路

這里我們使用axios進行接口的請求捕透,我們要用到axios的兩個功能--攔截器cancleToken聪姿。首先我們要使用攔截器碴萧,去攔截要發(fā)送的請求,然后對比我們本地緩存池末购,看是否有在緩存池中存在破喻,如果存在,則使用cancleToken直接取消請求盟榴,并從緩存池從返回數(shù)據(jù)曹质,如果不存在則正常請求,并在響應攔截器中將該條請求存入緩存池中曹货。當然咆繁,我們還需要一個過期時間,如果過期顶籽,則重新請求玩般,更新緩存池的數(shù)據(jù),避免一直在緩存池中取老數(shù)據(jù)礼饱。

流程

具體流程圖入下:


流程圖

提示
上圖右側的倆響應攔截器其實同屬一個攔截器坏为,這里為了區(qū)分,所以拆分成倆镊绪。

實現(xiàn)

import axios from 'axios'
// 定義一個緩存池用來緩存數(shù)據(jù)
let cache = {}
const EXPIRE_TIME = 60000
// 利用axios的cancelToken來取消請求
const CancelToken = axios.CancelToken

// 請求攔截器中用于判斷數(shù)據(jù)是否存在以及過期 未過期直接返回
axios.interceptors.request.use(config => {
  // 如果需要緩存--考慮到并不是所有接口都需要緩存的情況
  if (config.cache) {
    let source = CancelToken.source()
    config.cancelToken = source.token
    // 去緩存池獲取緩存數(shù)據(jù)
    let data = cache[config.url]
    // 獲取當前時間戳
    let expire_time = getExpireTime()
    // 判斷緩存池中是否存在已有數(shù)據(jù) 存在的話 再判斷是否過期
    // 未過期 source.cancel會取消當前的請求 并將內容返回到攔截器的err中
    if (data && expire_time - data.expire < EXPIRE_TIME) {
      source.cancel(data)
    }
  }
  return config
})

// 響應攔截器中用于緩存數(shù)據(jù) 如果數(shù)據(jù)沒有緩存的話
axios.interceptors.response.use(
  response => {
    // 只緩存get請求
    if (response.config.method === 'get' && response.config.cache) {
      // 緩存數(shù)據(jù) 并將當前時間存入 方便之后判斷是否過期
      let data = {
        expire: getExpireTime(),
        data: response.data
      }
      cache[`${response.config.url}`] = data
    }
    return response
  },
  error => {
    // 請求攔截器中的source.cancel會將內容發(fā)送到error中
    // 通過axios.isCancel(error)來判斷是否返回有數(shù)據(jù) 有的話直接返回給用戶
    if (axios.isCancel(error)) return Promise.resolve(error.message.data)
    // 如果沒有的話 則是正常的接口錯誤 直接返回錯誤信息給用戶
    return Promise.reject(error)
  }
)

// 獲取當前時間
function getExpireTime() {
  return new Date().getTime()
}

提示
之所以緩存邏輯寫在響應攔截器中是因為只有在響應攔截器中可以得到接口返回的數(shù)據(jù)匀伏,而請求攔截器中,無法做到蝴韭。

使用

然后頁面中接口請求如下配置:

<template>
  <div>
    i am page A
    <router-link to="/">回首頁</router-link>
  </div>
</template>

<script>
import axios from '../utils/axios'

export default {
  mounted () {
    // 加上屬性cache:true 則表示當前接口需要緩存(可以從緩存獲裙坏摺)
    axios('v2/book/1003078', {
      cache: true
    }).then(r => {
      console.log(r)
    })
  }
}
</script>

或者在統(tǒng)一的api接口管理文件中配置:

import axios from './axios'

export const getBooks = () => {
  return axios('v2/book/1003078', { cache: true })
}

簡單封裝

新建一個cache.js

// 緩存池
let CACHES = {}

export default class Cache {
  constructor(axios) {
    this.axios = axios
    this.cancelToken = axios.CancelToken
    this.options = {}
  }

  use(options) {
    let defaults = {
      expire: 60000, // 過期時間 默認一分鐘
      storage: false, // 是否開啟緩存
      storage_expire: 3600000, // 本地緩存過期時間 默認一小時
      instance: this.axios, // axios的實例對象 默認指向當前axios
      requestConfigFn: null, // 請求攔截的操作函數(shù) 參數(shù)為請求的config對象 返回一個Promise
      responseConfigFn: null, // 響應攔截的操作函數(shù) 參數(shù)為響應數(shù)據(jù)的response對象 返回一個Promise
      ...options
    }
    this.options = defaults
    this.init()
    // if (options && !options.instance) return this.options.instance
  }

  init() {
    // 初始化
    let options = this.options
    if (options.storage) {
      // 如果開啟本地緩存 則設置一個過期時間 避免時間過久 緩存一直存在
      this._storageExpire('expire').then(() => {
        if (localStorage.length === 0) CACHES = {}
        else mapStorage(localStorage, 'get')
      })
    }
    this.request(options.requestConfigFn)
    this.response(options.responseConfigFn)
  }

  request(cb) {
    // 請求攔截器
    let options = this.options
    options.instance.interceptors.request.use(async config => {
      // 判斷用戶是否返回 config 的 promise
      let newConfig = cb && (await cb(config))
      config = newConfig || config
      if (config.cache) {
        let source = this.cancelToken.source()
        config.cancelToken = source.token
        let data = CACHES[config.url]
        let expire = getExpireTime()
        // 判斷緩存數(shù)據(jù)是否存在 存在的話 是否過期 沒過期就返回
        if (data && expire - data.expire < this.options.expire) {
          source.cancel(data)
        }
      }
      return config
    })
  }

  response(cb) {
    // 響應攔截器
    this.options.instance.interceptors.response.use(
      async response => {
        // 判斷用戶是否返回了 response 的 Promise
        let newResponse = cb && (await cb(response))
        response = newResponse || response
        if (response.config.method === 'get' && response.config.cache) {
          let data = {
            expire: getExpireTime(),
            data: response
          }
          CACHES[`${response.config.url}`] = data
          if (this.options.storage) mapStorage(CACHES)
        }
        return response
      },
      error => {
        // 返回緩存數(shù)據(jù)
        if (this.axios.isCancel(error)) {
          return Promise.resolve(error.message.data)
        }
        return Promise.reject(error)
      }
    )
  }

  // 本地緩存過期判斷
  _storageExpire(cacheKey) {
    return new Promise(resolve => {
      let key = getStorage(cacheKey)
      let date = getExpireTime()
      if (key) {
        // 緩存存在 判斷是否過期
        let isExpire = date - key < this.options.storage_expire
        // 如果過期 則重新設定過期時間 并清空緩存
        if (!isExpire) {
          removeStorage()
        }
      } else {
        setStorage(cacheKey, date)
      }
      resolve()
    })
  }
}

/**
 * caches: 緩存列表
 * type: set->存 get->取
 */
function mapStorage(caches, type = 'set') {
  Object.entries(caches).map(([key, cache]) => {
    if (type === 'set') {
      setStorage(key, cache)
    } else {
      // 正則太弱 只能簡單判斷是否是json字符串
      let reg = /\{/g
      if (reg.test(cache)) CACHES[key] = JSON.parse(cache)
      else CACHES[key] = cache
    }
  })
}

// 清除本地緩存
function removeStorage() {
  localStorage.clear()
}

// 設置緩存
function setStorage(key, cache) {
  localStorage.setItem(key, JSON.stringify(cache))
}

// 獲取緩存
function getStorage(key) {
  let data = localStorage.getItem(key)
  return JSON.parse(data)
}

// 設置過期時間
function getExpireTime() {
  return new Date().getTime()
}

然后在自定義的axios.js中引入

import axios from 'axios'
import Cache from './cache2'

// axios的自定義實例
let instance = axios.create({
  baseURL: ''
})

let cache = new Cache(axios) // 將當前 axios 對象傳入 Cache 中
cache.use({
  expire: 30000,
  storage: true,
  instance, // 如果有自定義axios實例 比如上面的instance 需要將其傳入instance 沒有則不傳
  requestConfigFn: config => {
    // 請求攔截自定義操作
    if (config.header) {
      config.header.token = 'i am token'
    } else {
      config.header = { token: 'i am token' }
    }
    // 需要將config對象通過 Promise 返回 cache 中 也可以使用new Promise的寫法
    return Promise.resolve(config)
  },
  responseConfigFn: res => {
    // 響應攔截的自定義操作
    if (!res.data.code) {
      // 需要將 res 通過 Promise 返回
      return Promise.resolve(res)
    }
  }
})

export default instance

發(fā)布NPM

該工具已經(jīng)打包至 NPM 庫,可通過包命令安裝:

# npm
npm install axios-request-cache --save

# yarn
yarn add axios-request-cache

axios-request-cache

總結

  • cache.js可能還有些情況未考慮進去
  • requestConfigFnresponseConfigFn能操作的空間可能也不夠大

后續(xù)還會繼續(xù)優(yōu)化

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末榄鉴,一起剝皮案震驚了整個濱河市履磨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌庆尘,老刑警劉巖剃诅,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驶忌,居然都是意外死亡矛辕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門付魔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聊品,“玉大人,你說我怎么就攤上這事几苍⊙钆伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵擦剑,是天一觀的道長妖胀。 經(jīng)常有香客問我,道長惠勒,這世上最難降的妖魔是什么赚抡? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮纠屋,結果婚禮上涂臣,老公的妹妹穿的比我還像新娘。我一直安慰自己售担,他們只是感情好赁遗,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著族铆,像睡著了一般岩四。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哥攘,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天剖煌,我揣著相機與錄音,去河邊找鬼逝淹。 笑死耕姊,一個胖子當著我的面吹牛,可吹牛的內容都是我干的栅葡。 我是一名探鬼主播茉兰,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼欣簇!你這毒婦竟也來了规脸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤醉蚁,失蹤者是張志新(化名)和其女友劉穎燃辖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體网棍,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡黔龟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了滥玷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氏身。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惑畴,靈堂內的尸體忽然破棺而出蛋欣,到底是詐尸還是另有隱情,我是刑警寧澤如贷,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布陷虎,位于F島的核電站到踏,受9級特大地震影響,放射性物質發(fā)生泄漏尚猿。R本人自食惡果不足惜窝稿,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凿掂。 院中可真熱鬧伴榔,春花似錦、人聲如沸庄萎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糠涛。三九已至援奢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脱羡,已是汗流浹背萝究。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锉罐,地道東北人帆竹。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像脓规,于是被迫代替她去往敵國和親栽连。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內容