详解JavaScript 中的批处理和缓存
作者:rxliuli 发布时间:2024-04-28 09:48:03
场景
最近在生产环境遇到了下面这样一个场景:
后台在字典表中存储了一些之前需要前后端共同维护的枚举值,并提供根据 type/id 获取字典的 API。所以在渲染列表的时候,有很多列表的字段直接就是字典的 id,而没有经过后台的数据拼装。
起初,吾辈解决问题的流程如下
确定字典字段,添加转换后的对象类型接口
将对象列表进行转换得到其中字典字段的所有值
对字典 id 列表进行去重
根据 id 列表从后台获取到所有的字典数据
将获得的字典数据转换为 id => 字典 的 Map
遍历最初的列表,对里面指定的字典字段进行转换
可以看到,上面的步骤虽然不麻烦,但却十分繁琐,需要定义额外的类型不说,还很容易发生错误。
思路
使用 异步批处理 + 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


猜你喜欢
- json 模块Python 提供了内置的 json 模块来处理 JSON 格式的文件。该模块主要分为读取和写入 JSON 文件。读取 JSO
- 按需导入:安装插件首先需要引入额外的插件:前**vite-plugin-components已重命名为unplugin-vue-compon
- 一、内置函数下面简单介绍几个:1.abs() 求绝对值2.all() 如果 iterable 的所有元素都为真(或者如果可迭代为空),则返回
- queue模块简介queue模块是Python内置的标准模块,模块实现了三种类型的队列,它们的区别仅仅是条目取回的顺序,分别由3个类进行表示
- 接触过 Django 的同学都应该十分熟悉它的 ORM 系统。对于 python 新手而言,这是一项几乎可以被称作“黑科技”的特性:只要你在
- 前言在上一篇文章PyG搭建GCN前的准备:了解PyG中的数据格式中,大致了解了PyG中的数据格式,这篇文章主要是简单搭建GCN来实现节点分类
- 在网页上,有一些内容是通过执行Ajax请求动态加载数据渲染出来的。对于需要获取这些内容的需求,我们可以使用Python来实现数据的抓取。Aj
- 前言默认情况下SQL SERVER的安装路径与数据库的默认存放路径是在C盘的--这就很尴尬。平时又不注意,有天发现C盘的剩余空间比较吃紧了,
- 一:原理: 先看一下点击事件的执行顺序: 单击(click):mousedown,mouseout,click; 双击(dblclick):
- Python编程:函数函数是带名字的代码块,用于完成具体的工作。要执行函数定义的特定任务,可调用该函数。需要在程序中多次执行同一项任务时,你
- 这两条是关于IE环境中的CSS的。不要使用import引入CSS,可以避免内容的无样式瞬间(FOUC)问题。不要把样式的link放到页面后(
- 基于spring boot开发的微服务应用,与MyBatis如何集成?集成方法可行的方法有:1.基于XML或者Java Config,构建必
- 首先恭喜月影,当然希望好书大卖!原文提供了样章下载1.1M,pdf格式的。如果大家想下载可以访问源地址:http://bbs.51js.co
- 本篇文章适合css新手学习,对于已经掌握了css的朋友们也可以通过本片文章来复习知识。作者通过实践,认为在有些情况下css的代码是可以更加简
- 情景一: var yx01 = new function() {return "圆心"}; alert(yx01);我们
- css里关于浏览器的兼容问题一直困惑着我们初级的css用户(高手可直接绕过),这里想根据我前段时间拜读的李超的书籍《css网站布局实录》里学
- 常用时间转换及处理函数:import datetime# 获取当前时间d1 = datetime.datetime.now()print d
- 1、原材料1.1 花灯纸如下所示,还可以加上自己喜欢的图案、文字等。2.2 Python环境和模块一台安装了Python环境的电脑,Pyth
- mysql 加了 skip-name-resolve不能链接的问题,要确认 MySql 是否采用过主机名的授权在 MySql Server
- 今天在工作中遇到一个问题,郁闷了很久,特地写一篇博客记录一下,方便以后再遇到可以查找,也分享个各位小伙伴,在网上查找很多资料说用Vue.$s