【JS】针对过程的缓存

122 阅读3分钟

缓存机制是一种常见的优化技术,用于提高应用程序的性能和响应速度; 随着应用规模的变大,缓存可以明显地提高服务效率,节省服务成本; 此处结合前端实际场景,介绍一种适合 JS 单线程的内存缓存机制。

需求场景举例

前端请求复用

// 获取可选工作模式
async function getEnumPattern() {
  const res = await fetch('/api/enum/pattern');
  return res.json();
}
  • 配置平台提供了一套很少变动的枚举数据
  • 前端页面多个表单多个字段需要通过接口获取上述数据渲染下拉选项
  • 期望在页面未刷新的情况下无论用户如何切换功能模块都只请求一次上述接口

后端并发响应

// 响应工作模式请求
app.get('/api/enum/pattern', async () => {
  const patterns = await readDatabase('pattern');
  return { patterns };
});
  • 后端服务提供了一个返回相同且请求量巨大的接口
  • 需要在同一时间内响应不同页面对上述接口相同参数的请求
  • 期望相同或相近时间接到的多个请求只触发一次实际的操作并将结果分发多个返回

常规思路分析

let json;
async function getEnumPattern() {
  if (json) {
    return json;
  }
  const res = await fetch('/api/enum/pattern');
  json = await res.json();
  return json;
}

let patterns;
app.get('/api/enum/pattern', async () => {
  if (!patterns) {
    patterns = await readDatabase('pattern');
  }
  return { patterns };
});
  • 需要人工维护变量,代码分散,缺乏规范
  • 变量可能为空,容易错误使用
  • 短时间并发包括首次的多次,无法确保只实际触发一次
  • 业务逻辑和缓存逻辑常混在一起,难以区分,不利于代码复用

针对过程缓存

相比常规思路变量内直接缓存结果数据的方式,函数调用等操作过程也可以缓存。

// 获取可选工作模式
async function getEnumPattern() {
  const res = await fetch('/api/enum/pattern');
  return res.json();
}

// 添加缓存封装成新的函数
const getEnumPatternThroughCache = (() => {
  let loader;
  return async function() {
    return loader || (loader = getEnumPattern());
  }
})();
  • 旧函数不需要修改,且仍然可以使用
  • 新函数与旧函数参数列表、返回完全一致,调用方式一致
  • 新函数可以保证无论多短时间内连续的并发,都只会实际触发一次
  • 存储的过程与返回数据结构无关,更容易规范化
// 获取对应类型的可选工作模式
async function getEnumPattern(category) {
  const res = await fetch(`/api/enum/pattern?category=${category}`);
  return res.json();
}

// 添加缓存封装成新的函数
const getEnumPatternThroughCache = (() => {
  const loaders = {};
  return async function(category) {
    return loaders[category] || (loaders[category] = (async () => {
      try {
        const ret = await getEnumPattern(category);
  
        // 缓存有效期 10min
        setTimeout(() => {
          delete loaders[category];
        }, 600000);
  
        return ret;
      } catch (err) {
        // 可以不缓存错误
        delete loaders[category];

        throw err;
      }
    })());
  }
})();
  • 可以通过参数区分不同的过程
  • 缓存可以灵活的管理

总结

针对上述封装逻辑,本人开源了一个 NPM 包 @kitn/dope,支持 TypeScript 类型检查,欢迎大家体验和使用。

你也可能在自己的实践中已经使用过上述思路解决实际问题,欢迎留言分享看法和指出问题。

社区开源代码库中也有相似的缓存实现,例如 @apollo/clientjson-rules-engine 等,大家可以深入探索一下。