欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解JavaScript 中的批處理和緩存

 更新時(shí)間:2020年11月19日 09:27:31   作者:rxliuli  
這篇文章主要介紹了詳解JavaScript 中的批處理和緩存,幫助大家完成需求,更好的理解和使用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ù)拼裝。

起初,吾輩解決問題的流程如下

  1. 確定字典字段,添加轉(zhuǎn)換后的對(duì)象類型接口
  2. 將對(duì)象列表進(jìn)行轉(zhuǎn)換得到其中字典字段的所有值
  3. 對(duì)字典 id 列表進(jìn)行去重
  4. 根據(jù) id 列表從后臺(tái)獲取到所有的字典數(shù)據(jù)
  5. 將獲得的字典數(shù)據(jù)轉(zhuǎn)換為 id => 字典 的 Map
  6. 遍歷最初的列表,對(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)文章

最新評(píng)論