详解JavaScript 中的批处理和缓存


Posted in Javascript onNovember 19, 2020

场景

最近在生产环境遇到了下面这样一个场景:
后台在字典表中存储了一些之前需要前后端共同维护的枚举值,并提供根据 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 中间层负责处理数据转换。

以上就是详解JavaScript 中的批处理和缓存的详细内容,更多关于JavaScript 中的批处理和缓存的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
JavaScript实现Sleep函数的代码
Mar 04 Javascript
求数组最大最小值方法适用于任何数组
Aug 16 Javascript
利用JavaScript的AngularJS库制作电子名片的方法
Jun 18 Javascript
JS组件Bootstrap Table表格多行拖拽效果实现代码
Dec 08 Javascript
javascript获取网页各种高宽及位置的方法总结
Jul 27 Javascript
JS中input表单隐藏域及其使用方法
Feb 13 Javascript
关于Angular2 + node接口调试的解决方案
May 28 Javascript
js禁止浏览器页面后退功能的实例(推荐)
Sep 01 Javascript
vue.draggable实现表格拖拽排序效果
Dec 01 Javascript
微信小程序与后台PHP交互的方法实例分析
Dec 10 Javascript
vue.js购物车添加商品组件的方法
Sep 17 Javascript
JS实现拖动模糊框特效
Aug 25 Javascript
Javascript中window.name属性详解
Nov 19 #Javascript
JavaScript实现图片合成下载的示例
Nov 19 #Javascript
vue 获取到数据但却渲染不到页面上的解决方法
Nov 19 #Vue.js
vue 插槽简介及使用示例
Nov 19 #Vue.js
微信小程序实现点击导航条切换页面
Nov 19 #Javascript
详解Vue的mixin策略
Nov 19 #Vue.js
微信小程序自定义底部弹出框功能
Nov 18 #Javascript
You might like
使用phpQuery采集网页的方法
2013/11/13 PHP
浅谈php7的重大新特性
2015/10/23 PHP
TP5(thinkPHP框架)实现后台清除缓存功能示例
2019/05/29 PHP
PHP变量的作用范围实例讲解
2020/12/22 PHP
图片自动更新(说明)
2006/10/02 Javascript
实现png图片和png背景透明(支持多浏览器)的方法
2009/09/08 Javascript
jQuery与其它库冲突的解决方法
2010/06/25 Javascript
自写简单JS判断是否已经弹出页面
2010/10/20 Javascript
js focus不起作用的解决方法(主要是因为dom元素是否加载完成)
2010/11/05 Javascript
解决Jquery load()加载GB2312页面时出现乱码的两种方案
2013/09/10 Javascript
JavaScript中的操作符==与===介绍
2014/12/31 Javascript
jQuery菜单插件用法实例
2015/07/25 Javascript
Windows下用PyCharm和Visual Studio开始Python编程
2015/10/26 Javascript
jQuery实现简单滚动动画效果
2016/04/07 Javascript
关于Jquery中的bind(),on()绑定事件方式总结
2016/10/26 Javascript
Node.js 中使用 async 函数的方法
2017/11/20 Javascript
JS实现生成由字母与数字组合的随机字符串功能详解
2018/05/25 Javascript
详解vue.js根据不同环境(正式、测试)打包到不同目录
2018/07/13 Javascript
vue 项目中当访问路由不存在的时候默认访问404页面操作
2020/08/31 Javascript
如何在JavaScript中使用localStorage详情
2021/02/04 Javascript
[34:10]Secret vs VG 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.24
2019/09/10 DOTA
[49:02]KG vs Infamous 2019国际邀请赛淘汰赛 败者组BO1 8.20.mp4
2020/07/19 DOTA
python实现杨辉三角思路
2017/07/14 Python
Python实现基于POS算法的区块链
2018/08/07 Python
python3.4+pycharm 环境安装及使用方法
2019/06/13 Python
关于pycharm 切换 python3.9 报错 ‘HTMLParser‘ object has no attribute ‘unescape‘ 的问题
2020/11/24 Python
Python制作简单的剪刀石头布游戏
2020/12/10 Python
HTML5 Canvas——用路径描画线条实例介绍
2013/06/09 HTML / CSS
工程管理造价应届生求职信
2013/11/13 职场文书
留学生如何写好自荐信
2013/12/27 职场文书
学雷锋月活动总结
2014/04/25 职场文书
聋哑人盗窃罪辩护词
2015/05/21 职场文书
鲁冰花观后感
2015/06/10 职场文书
励志正能量20句:送给所有为梦想拼搏的人
2019/11/11 职场文书
Win11绿屏怎么办?Win11绿屏死机的解决方法
2021/11/21 数码科技
Windows server 2012搭建FTP服务器
2022/04/29 Servers