详解JavaScript 中的批处理和缓存

作者:rxliuli 时间:2024-04-28 09:48:03 

场景

最近在生产环境遇到了下面这样一个场景:
后台在字典表中存储了一些之前需要前后端共同维护的枚举值,并提供根据 type/id 获取字典的 API。所以在渲染列表的时候,有很多列表的字段直接就是字典的 id,而没有经过后台的数据拼装。

起初,吾辈解决问题的流程如下

  1. 确定字典字段,添加转换后的对象类型接口

  2. 将对象列表进行转换得到其中字典字段的所有值

  3. 对字典 id 列表进行去重

  4. 根据 id 列表从后台获取到所有的字典数据

  5. 将获得的字典数据转换为 id => 字典 的 Map

  6. 遍历最初的列表,对里面指定的字典字段进行转换

可以看到,上面的步骤虽然不麻烦,但却十分繁琐,需要定义额外的类型不说,还很容易发生错误。

思路

  • 使用 异步批处理 + LRU 缓存 优化性能

  • 支持异步 formatter 获得更好的使用体验

实现异步批处理

参考实现:


import { wait } from '../async/wait'

/**
* 将多个并发异步调用合并为一次批处理
* @param handle 批处理的函数
* @param ms 等待的时长(时间越长则可能合并的调用越多,否则将使用微任务只合并一次同步执行的所有调用)
*/
export function batch<P extends any[], R extends any>(
handle: (list: P[]) => Promise<Map<P, R | Error>>,
ms: number = 0,
): (...args: P) => Promise<R> {
//参数 => 结果 映射
const resultCache = new Map<string, R | Error>()
//参数 => 次数的映射
const paramCache = new Map<string, number>()
//当前是否被锁定
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
}
}

实现批处理的基本思路如下

1.使用 Map paramCache 缓存传入的 参数 => 剩余调用次数(该参数还需要查询几次结果)
2.使用 Map resultCache 缓存 参数 => 结果
3.使用 lock 标识当前是否有函数正在执行
4.满足以下条件需要等待
       Map 中不包含结果
       目前有其它调用在执行
       还未满最小等待时长(收集调用的最小时间片段)
5.使用 lock 标识正在执行
6.判断是否已经存在结果
       如果不存在则执行批处理处理当前所有的参数
7.从缓存 Map 中获取结果
8.将 paramCache 中对应参数的 剩余调用次数 -1
9.判断是否还需要保留该缓存(该参数对应的剩余调用次数为 0)
       不需要则删除
10.判断缓存的结果是否是 Error
        是的话则 throw 抛出错误

LRU 缓存

参考: Wiki 缓存算法, 实现 MemoryCache

问:这里为什么使用缓存?
答:这里的字典接口在大概率上是幂等的,所以可以使用缓存提高性能
问:那么缓存策略为什么要选择 LRU 呢?
答:毫无疑问 FIFO 是不合理的
问:那为什么不选择 LFU 算法呢?它似乎能保留访问最频繁的资源
答:因为字典表并非完全幂等,吾辈希望避免一种可能–访问最多的字典一直没有删除,而它在数据库已经被更新了。

大致实现思路如下

1.使用一个 Map 记录 缓存 key => 最后访问时间
2.每次获取缓存时更新最后访问时间
3.添加新的缓存时检查缓存数量
          如果超过最大数量,则删除最后访问时间距离现在最长的一个缓存
4.添加新的缓存
Pass: 不要吐槽性能很差啦,这个场景下不会缓存特别多的元素啦,最多也就不到 1000 个吧

结合高阶函数

现在,我们可以结合这两种方式了,同时使用 onceOfSameParam/batch 两个高阶函数来优化 根据 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 函数,但后来想想,如果 slot 里也包含字典 id 呢?那是否 slot 也要支持异步呢?这可是个比较棘手的问题,所以还是不支持好了。

最终,吾辈在组件与 API 之间添加了 *Service 中间层负责处理数据转换。

来源:https://blog.rxliuli.com/p/8dc80a64/?utm_source=tuicool&utm_medium=referral

标签:JavaScript,批处理,缓存
0
投稿

猜你喜欢

  • 用伪类:hover实现提示效果

    2008-05-29 12:59:00
  • Python爬虫+Tkinter制作一个翻译软件的示例

    2023-12-14 07:22:42
  • vue pages 多入口项目 + chainWebpack 全局引用缩写说明

    2024-05-21 10:30:37
  • winxp 安装MYSQL 出现Error 1045 access denied 的解决方法

    2024-01-20 20:53:09
  • python语音信号处理详细教程

    2022-02-23 11:19:37
  • 使用Python3编写抓取网页和只抓网页图片的脚本

    2023-08-15 08:00:17
  • django form和field具体方法和属性说明

    2023-07-24 11:22:45
  • 在SQL Server2000中恢复Master数据库

    2008-01-05 14:05:00
  • python基础之文件操作

    2022-12-23 01:15:53
  • 关于Python解包知识点总结

    2021-01-03 22:34:15
  • Python入门教程(五)Python变量的用法

    2021-04-05 11:17:35
  • MySQL时间设置注意事项的深入总结

    2024-01-16 04:39:52
  • SQL服务器无法启动的解决方法

    2024-01-16 04:47:20
  • python使用matplotlib绘制折线图

    2021-08-23 05:12:53
  • asp dictionary对象的用法

    2011-04-14 11:09:00
  • asp智能脏话过滤系统v1.0

    2011-04-14 11:00:00
  • ES6/JavaScript使用技巧分享

    2024-04-30 10:10:04
  • Python实现的KMeans聚类算法实例分析

    2021-06-28 04:44:49
  • vue 使用localstorage实现面包屑的操作

    2024-05-10 14:19:40
  • YOLOv5改进之添加CBAM注意力机制的方法

    2023-07-22 20:48:52
  • asp之家 网络编程 m.aspxhome.com