详解JavaScript 中的批处理和缓存
场景
最近在生产环境遇到了下面这样一个场景:
后台在字典表中存储了一些之前需要前后端共同维护的枚举值,并提供根据type/id获取字典的API。所以在渲染列表的时候,有很多列表的字段直接就是字典的id,而没有经过后台的数据拼装。
起初,吾辈解决问题的流程如下
- 确定字典字段,添加转换后的对象类型接口
- 将对象列表进行转换得到其中字典字段的所有值
- 对字典id列表进行去重
- 根据id列表从后台获取到所有的字典数据
- 将获得的字典数据转换为id=>字典的Map
- 遍历最初的列表,对里面指定的字典字段进行转换
可以看到,上面的步骤虽然不麻烦,但却十分繁琐,需要定义额外的类型不说,还很容易发生错误。
思路
- 使用异步批处理+LRU缓存优化性能
- 支持异步formatter获得更好的使用体验
实现异步批处理
参考实现:
import{wait}from'../async/wait'
/**
*将多个并发异步调用合并为一次批处理
*@paramhandle批处理的函数
*@paramms等待的时长(时间越长则可能合并的调用越多,否则将使用微任务只合并一次同步执行的所有调用)
*/
exportfunctionbatch(
handle:(list:P[])=>Promise
实现批处理的基本思路如下
1.使用MapparamCache缓存传入的参数=>剩余调用次数(该参数还需要查询几次结果)
2.使用MapresultCache缓存参数=>结果
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了。
constgetById=onceOfSameParam(
batch<[number],Dict>(async(idList)=>{
if(idList.length===0){
returnnewMap()
}
//一次批量处理多个id
constlist=awaitthis.getByIdList(uniqueBy(idList.flat()))
returnarrayToMap(
list,
(dict)=>[dict.id],
(dict)=>dict,
)
},100),
)
支持异步formatter
原本想要支持ListTable的异步formatter函数,但后来想想,如果slot里也包含字典id呢?那是否slot也要支持异步呢?这可是个比较棘手的问题,所以还是不支持好了。
最终,吾辈在组件与API之间添加了*Service中间层负责处理数据转换。
以上就是详解JavaScript中的批处理和缓存的详细内容,更多关于JavaScript中的批处理和缓存的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。