相親交友原始碼開發,前端API如何請求快取?

雲豹科技程式設計師發表於2021-11-24
在開發相親交友原始碼時,效能都是必不可少的話題。
事實上,快取一定是提升相親交友原始碼效能最有效方法之一,尤其是使用者受限於網速的情況下。提升系統的響應能力,降低網路的消耗。當然,內容越接近於使用者,則快取的速度就會越快,快取的有效性則會越高。
以相親交友原始碼客戶端而言,我們有很多快取資料與資源的方法,例如 標準的瀏覽器快取 以及 目前火熱的 Service worker。但是,他們更適合靜態內容的快取。例如 html,js,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
}
第一行程式碼 使用了 es6以上的 Map,如果對map不是很理解的情況下,你可以參考ECMAScript 6 入門 Set 和 Map 或者 Exploring ES6 關於 map 和 set的介紹,此處可以理解為一個鍵值對儲存結構。
之後 程式碼 使用 了 async 函式,可以將相親交友原始碼中的非同步操作變得更為方便。你可以參考ECMAScript 6 入門 async函式來進行學習或者鞏固知識。
程式碼本身很容易理解,是利用 Map 物件對資料進行快取,之後呼叫從 Map 物件來取資料。對於及其簡單的業務場景,直接利用此程式碼即可。
呼叫方式:
getWares().then( ... )
// 第二次呼叫 取得先前的data
getWares().then( ... )

方案二 promise 快取

方案一本身是不足的。因為如果相親交友原始碼考慮同時兩個以上的呼叫此 api,會因為請求未返回而進行第二次請求api。當然,如果你在相親交友原始碼中新增類似於 vuex、redux這樣的單一資料來源框架,這樣的問題不太會遇到,但是有時候我們想在各個複雜元件分別呼叫api,而不想對元件進行元件通訊資料時候,便會遇到此場景。
const promiseCache = new Map()
getWares() {
    const key = 'wares'
    let promise = promiseCache.get(key);
    // 當前promise快取中沒有 該promise
    if (!promise) {
        promise = request.get('/getWares').then(res => {
            // 對res 進行操作
            ...
        }).catch(error => {
            // 在請求回來後,如果出現問題,把promise從cache中刪除 以避免第二次請求繼續出錯S
            promiseCache.delete(key)
            return Promise.reject(error)
        })
    }
    // 返回promise
    return promise
}
該程式碼避免了方案一的同一時間多次請求的問題。同時也在相親交友原始碼後端出錯的情況下對promise進行了刪除,不會出現快取了錯誤的promise就一直出錯的問題。
呼叫方式:
getWares().then( ... )
// 第二次呼叫 取得先前的promise
getWares().then( ... )

方案三 多promise 快取

該方案是同時需要 一個以上 的api請求的情況下,對資料同時返回,如果某一個api發生錯誤的情況下。均不返回正確資料。
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 刪除即可,此時我們呼叫方法就可以向伺服器進行請求。這樣我們規避了前端顯示舊的的資料。但是我們可能一段時間沒有對資料進行操作,那麼此時舊的資料就一直存在,那麼我們最好規定個時間來去除資料。該方案是採用了 類 持久化資料來做資料快取,同時新增了過期時長資料以及引數化。程式碼如下:首先定義持久化類,該類可以儲存 promise 或者 data
class ItemCache() {
    construct(data, timeout) {
        this.data = data
        // 設定超時時間,設定為多少秒
        this.timeout = timeout
        // 建立物件時候的時間,大約設定為資料獲得的時間
        this.cacheTime = (new Date()).getTime
    }
}
然後我們定義該資料快取。我們採用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)
    }
}
此時相親交友原始碼的資料類以及操作類 都已經定義好,我們可以在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
}
該方案使用了 相親交友原始碼過期時間 和 api 引數不同而進行 快取的方式。已經可以滿足絕大部分的業務場景。
呼叫方式
getWares(1,2).then( ... )
// 第二次呼叫 取得先前的promise
getWares(1,2).then( ... )
// 不同的引數,不取先前promise
getWares(1,3).then( ... )

方案五 基於修飾器的方案四

和方案四的解法一致的,但是是基於修飾器來做。程式碼如下:
// 生成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(_) {
        return generateKeyError
    }
}
function decorate(handleDescription, entryArgs) {
    // 判斷 當前 最後資料是否是descriptor,如果是descriptor,直接 使用
    // 例如 log 這樣的修飾器
    if (isDescriptor(entryArgs[entryArgs.length - 1])) {
        return handleDescription(...entryArgs, [])
    } else {
        // 如果不是
        // 例如 add(1) plus(20) 這樣的修飾器
        return function() {
            return handleDescription(...Array.protptype.slice.call(arguments), entryArgs)
        }
    }
}
function handleApiCache(target, name, descriptor, ...config) {
    // 拿到函式體並儲存
    const fn = descriptor.value
    // 修改函式體
    descriptor.value = function () { 
        const key =  generateKey(name, arguments)
        // key無法生成,直接請求 服務端資料
        if (key === generateKeyError)  {
            // 利用剛才儲存的函式體進行請求
            return fn.apply(null, arguments)
        }
        let promise = ExpriesCache.get(key)
        if (!promise) {
            // 設定promise
            promise = fn.apply(null, arguments).catch(error => {
                 // 在請求回來後,如果出現問題,把promise從cache中刪除
                ExpriesCache.delete(key)
                // 返回錯誤
                return Promise.reject(error)
            })
            // 使用 10s 快取,10s之後再次get就會 獲取null 而從服務端繼續請求
            ExpriesCache.set(key, promise, config[0])
        }
        return promise 
    }
    return descriptor;
}
// 制定 修飾器
function ApiCache(...args) {
    return decorate(handleApiCache, args)
}
此時 我們就會使用 類來對相親交友原始碼的api進行快取
class Api {
    // 快取10s
    @ApiCache(10)
    // 此時不要使用預設值,因為當前 修飾器 取不到
    getWare(params1, params2) {
        return request.get('/getWares')
    }
}
因為函式存在函式提升,所以沒有辦法利用函式來做 修飾器 例如:
var counter = 0;
var add = function () {
  counter++;
};
@add
function foo() {
}
該程式碼意圖是執行後counter等於 1,但是實際上結果是counter等於 0。因為函式提升,使得實際執行的程式碼是下面這樣
@add
function foo() {
}
var counter;
var add;
counter = 0;
add = function () {
  counter++;
};
所以沒有 辦法在相親交友原始碼函式上用修飾器。具體參考ECMAScript 6 入門 Decorator此方式寫法簡單且對業務層沒有太多影響。但是不可以動態修改 快取時間
呼叫方式
getWares(1,2).then( ... )
// 第二次呼叫 取得先前的promise
getWares(1,2).then( ... )
// 不同的引數,不取先前promise
getWares(1,3).then( ... )

總結

相親交友原始碼api 的快取機制與場景在這裡也基本上介紹了,基本上能夠完成絕大多數的資料業務快取,希望以上內容能對大家有幫助。
本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理 原文連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2843918/,如需轉載,請註明出處,否則將追究法律責任。

相關文章