前端api请求缓存方案

【序】缓存是提升性能最好的方法,尤其是用户受限于网速的情况下,我们可以使用service worker来缓存静态内容例如js,html,css以及图片等文件,而缓存系统数据,是本章所要探讨的内容前端api请求缓存方案

方案一 数据缓存

简单的数据缓存,第一次请求获取数据,之后便使用数据,不再请求后端api,代码如下:

const dataCache = new Map()

async getWares() {
    let key = 'wares'
    // 从data 缓存中获取 数据
    let data = dataCache.get(key)
    if (!data) {
        // 没有数据请求服务器
        const res = await request.get('/getWares')

        // 其他操作
        ...
        data = ...

        // 设置数据缓存
        dataCache.set(key, data)

    }
    return data
}

其核心原理在于,使用map进行数据存储,之后的数据都从map中读取,并且使用了async,返回一个promise,当map中有数据就返回map中的数据,当map中没有数据就去请求并返回

【缺陷】当第一次同时调用两次该api,会因为第一还没返回而进行两次网络请求

【解决】
1.使用vuex这样的单一数据源框架

  1. 不缓存data而是缓存promise

方案二 promise缓存

const dataCache = new Map()

async getWares() {
    let key = 'wares'
    // 从data 缓存中获取 数据
    let data = dataCache.get(key)
    if (!data) {
        // 没有数据请求服务器
        const res = await request.get('/getWares')

        // 其他操作
        ...
        data = ...

        // 设置数据缓存
        dataCache.set(key, data)

    }
    return data
}

【问题】

  1. 当需要多个api必须同时请求的情况下,对数据同时返回,并且某一api发生错误均不返回正确数据的情况无法处理
  2. 解决办法,使用多promise缓存

方案三 多promise缓存

const querys ={
    wares: 'getWares',
    skus: 'getSku'
}
const promiseCache = new Map()

async queryAll(queryApiName) {
    // 判断传入的数据是否是数组
    const queryIsArray = Array.isArray(queryApiName)
    // 统一化处理数据,无论是字符串还是数组均视为数组
    const apis = queryIsArray ? queryApiName : [queryApiName]

    // 获取所有的 请求服务
    const promiseApi = []

    apis.forEach(api => {
        // 利用promise 
        let promise = promiseCache.get(api)

        if (promise) {
            // 如果 缓存中有,直接push
            promise.push(promise)
        } else {
             promise = request.get(querys[api]).then(res => {
                // 对res 进行操作
                ...
                }).catch(error => {
                // 在请求回来后,如果出现问题,把promise从cache中删除
                promiseCache.delete(api)
                return Promise.reject(error)
            })
            promiseCache.set(api, promise)
            promiseCache.push(promise)
        }
    })
    return Promise.all(promiseApi).then(res => {
        // 根据传入的 是字符串还是数组来返回数据,因为本身都是数组操作
        // 如果传入的是字符串,则需要取出操作
        return queryIsArray ? res : res[0]
    })
}

调用时也可以兼顾

queryAll('wares').then( ... )
// 第二次调用 不会去取 wares,只会去skus
queryAll(['wares', 'skus']).then( ... )

【问题】:无法控制缓存的时间和时机
【解决】:添加时间有关的缓存

方案四 添加时间有关的缓存

【问题】如果我们在知道了修改了请求参数的情况下,把cache删除即可重新请求即可,但是在发起请求期间会展示旧数据,为了避免这样的问题就使用过期时长的问题

  1. 定义持久化类,该类可以存储promise或者data
class ItemCache() {
    construct(data, timeout) {
        this.data = data
        // 设定超时时间,设定为多少秒
        this.timeout = timeout
        // 创建对象时候的时间,大约设定为数据获得的时间
        this.cacheTime = (new Date()).getTime
    }
}
  1. 定义该数据缓存,我们采用map基本相同的api
class ExpriesCache {
    // 定义静态数据map来作为缓存池
    static cacheMap =  new Map()

    // 数据是否超时
    static isOverTime(name) {
        const data = ExpriesCache.cacheMap.get(name)

        // 没有数据 一定超时
        if (!data) return true

        // 获取系统当前时间戳
        const currentTime = (new Date()).getTime()        

        // 获取当前时间与存储时间的过去的秒数
        const overTime = (currentTime - data.cacheTime) / 1000

        // 如果过去的秒数大于当前的超时时间,也返回null让其去服务端取数据
        if (Math.abs(overTime) > data.timeout) {
            // 此代码可以没有,不会出现问题,但是如果有此代码,再次进入该方法就可以减少判断。
            ExpriesCache.cacheMap.delete(name)
            return true
        }

        // 不超时
        return false
    }

    // 当前data在 cache 中是否超时
    static has(name) {
        return !ExpriesCache.isOverTime(name)
    }

    // 删除 cache 中的 data
    static delete(name) {
        return ExpriesCache.cacheMap.delete(name) 
    }

    // 获取
    static get(name) {
        const isDataOverTiem = ExpriesCache.isOverTime(name)
        //如果 数据超时,返回null,但是没有超时,返回数据,而不是 ItemCache 对象
        return isDataOverTiem ? null : ExpriesCache.cacheMap.get(name).data
    }

    // 默认存储20分钟
    static set(name, data, timeout = 1200) {
        // 设置 itemCache
        const itemCache = mew ItemCache(data, timeout)
        //缓存
        ExpriesCache.cacheMap.set(name, itemCache)
    }
}
  1. 数据类以及操作类都定义号了,可以这样定义api层
// 生成key值错误
const generateKeyError = new Error("Can't generate key from name and argument")

// 生成key值
function generateKey(name, argument) {
    // 从arguments 中取得数据然后变为数组
    const params = Array.from(argument).join(',')

    try{
        // 返回 字符串,函数名 + 函数参数
        return `${name}:${params}`
    }catch(_) {
        // 返回生成key错误
        return generateKeyError
    }
}

async getWare(params1, params2) {
    // 生成key
    const key = generateKey('getWare', [params1, params2]) 
    // 获得数据
    let data = ExpriesCache.get(key)
    if (!data) {
        const res = await request('/getWares', {params1, params2})
        // 使用 10s 缓存,10s之后再次get就会 获取null 而从服务端继续请求
        ExpriesCache.set(key, res, 10)
    }
    return data
}
  1. 调用方式
getWares(1,2).then( ... )
// 第二次调用 取得先前的promise
getWares(1,2).then( ... )
// 不同的参数,不取先前promise
getWares(1,3).then( ... )
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容