詳解JavaScript 中的批處理和緩存
場(chǎng)景
最近在生產(chǎn)環(huán)境遇到了下面這樣一個(gè)場(chǎng)景:
后臺(tái)在字典表中存儲(chǔ)了一些之前需要前后端共同維護(hù)的枚舉值,并提供根據(jù) type/id 獲取字典的 API。所以在渲染列表的時(shí)候,有很多列表的字段直接就是字典的 id,而沒有經(jīng)過后臺(tái)的數(shù)據(jù)拼裝。
起初,吾輩解決問題的流程如下
- 確定字典字段,添加轉(zhuǎn)換后的對(duì)象類型接口
- 將對(duì)象列表進(jìn)行轉(zhuǎn)換得到其中字典字段的所有值
- 對(duì)字典 id 列表進(jìn)行去重
- 根據(jù) id 列表從后臺(tái)獲取到所有的字典數(shù)據(jù)
- 將獲得的字典數(shù)據(jù)轉(zhuǎn)換為 id => 字典 的 Map
- 遍歷最初的列表,對(duì)里面指定的字典字段進(jìn)行轉(zhuǎn)換
可以看到,上面的步驟雖然不麻煩,但卻十分繁瑣,需要定義額外的類型不說,還很容易發(fā)生錯(cuò)誤。
思路
- 使用 異步批處理 + LRU 緩存 優(yōu)化性能
- 支持異步 formatter 獲得更好的使用體驗(yàn)
實(shí)現(xiàn)異步批處理
參考實(shí)現(xiàn):
import { wait } from '../async/wait' /** * 將多個(gè)并發(fā)異步調(diào)用合并為一次批處理 * @param handle 批處理的函數(shù) * @param ms 等待的時(shí)長(時(shí)間越長則可能合并的調(diào)用越多,否則將使用微任務(wù)只合并一次同步執(zhí)行的所有調(diào)用) */ export function batch<P extends any[], R extends any>( handle: (list: P[]) => Promise<Map<P, R | Error>>, ms: number = 0, ): (...args: P) => Promise<R> { //參數(shù) => 結(jié)果 映射 const resultCache = new Map<string, R | Error>() //參數(shù) => 次數(shù)的映射 const paramCache = new Map<string, number>() //當(dāng)前是否被鎖定 let lock = false return async function (...args: P) { const key = JSON.stringify(args) paramCache.set(key, (paramCache.get(key) || 0) + 1) await Promise.all([wait(() => resultCache.has(key) || !lock), wait(ms)]) if (!resultCache.has(key)) { try { lock = true Array.from( await handle(Array.from(paramCache.keys()).map((v) => JSON.parse(v))), ).forEach(([k, v]) => { resultCache.set(JSON.stringify(k), v) }) } finally { lock = false } } const value = resultCache.get(key)! paramCache.set(key, paramCache.get(key)! - 1) if ((paramCache.get(key) || 0) <= 0) { paramCache.delete(key) resultCache.delete(key) } if (value instanceof Error) { resultCache.delete(key) throw value } return value as R } }
實(shí)現(xiàn)批處理的基本思路如下
1.使用 Map paramCache 緩存?zhèn)魅氲?參數(shù) => 剩余調(diào)用次數(shù)(該參數(shù)還需要查詢幾次結(jié)果)
2.使用 Map resultCache 緩存 參數(shù) => 結(jié)果
3.使用 lock 標(biāo)識(shí)當(dāng)前是否有函數(shù)正在執(zhí)行
4.滿足以下條件需要等待
Map 中不包含結(jié)果
目前有其它調(diào)用在執(zhí)行
還未滿最小等待時(shí)長(收集調(diào)用的最小時(shí)間片段)
5.使用 lock 標(biāo)識(shí)正在執(zhí)行
6.判斷是否已經(jīng)存在結(jié)果
如果不存在則執(zhí)行批處理處理當(dāng)前所有的參數(shù)
7.從緩存 Map 中獲取結(jié)果
8.將 paramCache 中對(duì)應(yīng)參數(shù)的 剩余調(diào)用次數(shù) -1
9.判斷是否還需要保留該緩存(該參數(shù)對(duì)應(yīng)的剩余調(diào)用次數(shù)為 0)
不需要?jiǎng)t刪除
10.判斷緩存的結(jié)果是否是 Error
是的話則 throw 拋出錯(cuò)誤
LRU 緩存
參考: Wiki 緩存算法, 實(shí)現(xiàn) MemoryCache
問:這里為什么使用緩存?
答:這里的字典接口在大概率上是冪等的,所以可以使用緩存提高性能
問:那么緩存策略為什么要選擇 LRU 呢?
答:毫無疑問 FIFO 是不合理的
問:那為什么不選擇 LFU 算法呢?它似乎能保留訪問最頻繁的資源
答:因?yàn)樽值浔聿⒎峭耆珒绲?,吾輩希望避免一種可能–訪問最多的字典一直沒有刪除,而它在數(shù)據(jù)庫已經(jīng)被更新了。
大致實(shí)現(xiàn)思路如下
1.使用一個(gè) Map 記錄 緩存 key => 最后訪問時(shí)間
2.每次獲取緩存時(shí)更新最后訪問時(shí)間
3.添加新的緩存時(shí)檢查緩存數(shù)量
如果超過最大數(shù)量,則刪除最后訪問時(shí)間距離現(xiàn)在最長的一個(gè)緩存
4.添加新的緩存
Pass: 不要吐槽性能很差啦,這個(gè)場(chǎng)景下不會(huì)緩存特別多的元素啦,最多也就不到 1000 個(gè)吧
結(jié)合高階函數(shù)
現(xiàn)在,我們可以結(jié)合這兩種方式了,同時(shí)使用 onceOfSameParam/batch 兩個(gè)高階函數(shù)來優(yōu)化 根據(jù) id 獲取字典信息 的 API 了。
const getById = onceOfSameParam( batch<[number], Dict>(async (idList) => { if (idList.length === 0) { return new Map() } // 一次批量處理多個(gè) id const list = await this.getByIdList(uniqueBy(idList.flat())) return arrayToMap( list, (dict) => [dict.id], (dict) => dict, ) }, 100), )
支持異步 formatter
原本想要支持 ListTable 的異步 formatter
函數(shù),但后來想想,如果 slot
里也包含字典 id 呢?那是否 slot
也要支持異步呢?這可是個(gè)比較棘手的問題,所以還是不支持好了。
最終,吾輩在組件與 API 之間添加了 *Service 中間層負(fù)責(zé)處理數(shù)據(jù)轉(zhuǎn)換。
以上就是詳解JavaScript 中的批處理和緩存的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 中的批處理和緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Egg.js構(gòu)建一個(gè)stream流式接口服務(wù)實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Egg.js構(gòu)建一個(gè)stream流式接口服務(wù)實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09JS+Canvas實(shí)現(xiàn)動(dòng)態(tài)時(shí)鐘效果
這篇文章主要為大家詳細(xì)介紹了JS+Canvas實(shí)現(xiàn)動(dòng)態(tài)時(shí)鐘效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10JavaScript 數(shù)組- Array的方法總結(jié)(推薦)
下面小編就為大家?guī)硪黄狫avaScript 數(shù)組- Array的方法總結(jié)(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07element-ui 時(shí)間選擇器限制范圍的實(shí)現(xiàn)(隨動(dòng))
這篇文章主要介紹了element-ui 時(shí)間選擇器限制范圍(隨動(dòng)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01openlayers實(shí)現(xiàn)圖標(biāo)拖動(dòng)獲取坐標(biāo)
這篇文章主要為大家詳細(xì)介紹了openlayers實(shí)現(xiàn)圖標(biāo)拖動(dòng)獲取坐標(biāo),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09JavaScript禁止右擊保存圖片,禁止拖拽圖片的實(shí)現(xiàn)代碼
這篇文章主要介紹了JavaScript禁止右擊保存圖片,禁止拖拽圖片的實(shí)現(xiàn)代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04JavaScript Perfection kill 測(cè)試及答案
近日,在Perfection kill上看到有關(guān)javascript quiz。并做了一下,最終錯(cuò)了2個(gè)(#2,#9),但是,這2道題,在Ie和ff下的答案是不一樣的?!2010-03-03微信小程序?qū)崿F(xiàn)消息框彈出動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)消息框彈出動(dòng)畫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06