詳解JavaScript 中的批處理和緩存
場景
最近在生產(chǎn)環(huán)境遇到了下面這樣一個場景:
后臺在字典表中存儲了一些之前需要前后端共同維護(hù)的枚舉值,并提供根據(jù) type/id 獲取字典的 API。所以在渲染列表的時候,有很多列表的字段直接就是字典的 id,而沒有經(jīng)過后臺的數(shù)據(jù)拼裝。
起初,吾輩解決問題的流程如下
- 確定字典字段,添加轉(zhuǎn)換后的對象類型接口
- 將對象列表進(jìn)行轉(zhuǎn)換得到其中字典字段的所有值
- 對字典 id 列表進(jìn)行去重
- 根據(jù) id 列表從后臺獲取到所有的字典數(shù)據(jù)
- 將獲得的字典數(shù)據(jù)轉(zhuǎn)換為 id => 字典 的 Map
- 遍歷最初的列表,對里面指定的字典字段進(jìn)行轉(zhuǎn)換
可以看到,上面的步驟雖然不麻煩,但卻十分繁瑣,需要定義額外的類型不說,還很容易發(fā)生錯誤。
思路
- 使用 異步批處理 + LRU 緩存 優(yōu)化性能
- 支持異步 formatter 獲得更好的使用體驗(yàn)
實(shí)現(xiàn)異步批處理
參考實(shí)現(xiàn):
import { wait } from '../async/wait'
/**
* 將多個并發(fā)異步調(diào)用合并為一次批處理
* @param handle 批處理的函數(shù)
* @param ms 等待的時長(時間越長則可能合并的調(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)識當(dāng)前是否有函數(shù)正在執(zhí)行
4.滿足以下條件需要等待
Map 中不包含結(jié)果
目前有其它調(diào)用在執(zhí)行
還未滿最小等待時長(收集調(diào)用的最小時間片段)
5.使用 lock 標(biāo)識正在執(zhí)行
6.判斷是否已經(jīng)存在結(jié)果
如果不存在則執(zhí)行批處理處理當(dāng)前所有的參數(shù)
7.從緩存 Map 中獲取結(jié)果
8.將 paramCache 中對應(yīng)參數(shù)的 剩余調(diào)用次數(shù) -1
9.判斷是否還需要保留該緩存(該參數(shù)對應(yīng)的剩余調(diào)用次數(shù)為 0)
不需要則刪除
10.判斷緩存的結(jié)果是否是 Error
是的話則 throw 拋出錯誤
LRU 緩存
參考: Wiki 緩存算法, 實(shí)現(xiàn) MemoryCache
問:這里為什么使用緩存?
答:這里的字典接口在大概率上是冪等的,所以可以使用緩存提高性能
問:那么緩存策略為什么要選擇 LRU 呢?
答:毫無疑問 FIFO 是不合理的
問:那為什么不選擇 LFU 算法呢?它似乎能保留訪問最頻繁的資源
答:因?yàn)樽值浔聿⒎峭耆珒绲?,吾輩希望避免一種可能–訪問最多的字典一直沒有刪除,而它在數(shù)據(jù)庫已經(jīng)被更新了。
大致實(shí)現(xiàn)思路如下
1.使用一個 Map 記錄 緩存 key => 最后訪問時間
2.每次獲取緩存時更新最后訪問時間
3.添加新的緩存時檢查緩存數(shù)量
如果超過最大數(shù)量,則刪除最后訪問時間距離現(xiàn)在最長的一個緩存
4.添加新的緩存
Pass: 不要吐槽性能很差啦,這個場景下不會緩存特別多的元素啦,最多也就不到 1000 個吧
結(jié)合高階函數(shù)
現(xiàn)在,我們可以結(jié)合這兩種方式了,同時使用 onceOfSameParam/batch 兩個高階函數(shù)來優(yōu)化 根據(jù) id 獲取字典信息 的 API 了。
const getById = onceOfSameParam(
batch<[number], Dict>(async (idList) => {
if (idList.length === 0) {
return new Map()
}
// 一次批量處理多個 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 也要支持異步呢?這可是個比較棘手的問題,所以還是不支持好了。
最終,吾輩在組件與 API 之間添加了 *Service 中間層負(fù)責(zé)處理數(shù)據(jù)轉(zhuǎn)換。
以上就是詳解JavaScript 中的批處理和緩存的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 中的批處理和緩存的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
移動端觸屏幻燈片圖片切換插件idangerous swiper.js
這篇文章主要為大家詳細(xì)介紹了移動端觸屏幻燈片圖片切換插件idangerous swiper.js的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
JS生成不重復(fù)隨機(jī)數(shù)組的函數(shù)代碼
這篇文章主要介紹了JS生成不重復(fù)隨機(jī)數(shù)組的函數(shù)代碼,需要的朋友可以參考下2014-06-06
JavaScript實(shí)現(xiàn)的3D旋轉(zhuǎn)魔方動畫效果實(shí)例代碼
在本篇文章里小編給大家整理了關(guān)于JavaScript實(shí)現(xiàn)的3D旋轉(zhuǎn)魔方動畫效果實(shí)例代碼,有興趣的朋友們測試下。2019-07-07
javascript實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼案例
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07
javascript為下拉列表動態(tài)添加數(shù)據(jù)項(xiàng)
這篇文章主要介紹了javascript如何為下拉列表動態(tài)添加數(shù)據(jù)項(xiàng),需要的朋友可以參考下2014-05-05
IE不出現(xiàn)Flash激活框的小發(fā)現(xiàn)的js實(shí)現(xiàn)方法
IE不出現(xiàn)Flash激活框的小發(fā)現(xiàn)的js實(shí)現(xiàn)方法...2007-09-09
js中Array.forEach跳出循環(huán)的方法實(shí)例
相信大家都知道forEach適用于只是進(jìn)行集合或數(shù)組遍歷,for則在較復(fù)雜的循環(huán)中效率更高,下面這篇文章主要給大家介紹了關(guān)于js中Array.forEach跳出循環(huán)的相關(guān)資料,需要的朋友可以參考下2021-09-09
Javascript 類的繼承實(shí)現(xiàn)代碼
JavaScript中類的學(xué)習(xí),從基本類繼承過來方法。2009-07-07

