前端大并发除了频控和QPS,还可以极致缓存

背景

前面写了网络请求的「频控」和「QPS」的文章:

可以想象,有这两种需求,肯定是有大量并发请求 的场景。除了控制请求的频率,还有一个思路也很有效果:去除冗余的重复请求。毫无疑问,要延续我们优雅的低侵入风格,Decorator 继续走起。

正文

思路

去除冗余的重复请求,最简单直接的思路就是把相同请求的结果缓存起来。下次发现请求相同,直接从内存读取结果返回,直接就不用发请求了。当然,还有一些细节需要处理,比如:

  1. 什么时候清除缓存?
  2. 如果前面的请求还没有返回,又发起了相同的请求怎么办?
  3. 如果返回报异常怎么处理?
  4. ......

这些点用语言解释都太苍白了,直接看代码,一眼就懂的事儿。但是,第 2 点是比较关键也有一定的难度,所以我们稍微展开说说。

现在主流的异步操作,都是使用的 Promise,我们这次也不例外。所以,如果前序请求 A1 还没有完成,重复的 A2 请求就发出了。我们只需要给 A2 返回 A1 的 Promise 就可以了。同理,A3、A4、A5......不管多少个请求来了,都是返回 A1 的 Promise。是不是听起来就很爽?!

什么?!还是不太明白?!

没关系,直接看代码。Talk is cheap, show me the code!。

代码

ts 复制代码
// 定义 requestCache 装饰器工厂函数
export const requestCache = (
  originalFunction: (...args: any[]) => Promise<any>,
  cacheDuration: number,
) => {
  const cache: Map<string, any> = new Map();
  const cachePromise: Map<string, Promise<any>> = new Map();

  // 返回一个装饰器函数
  return (...args: any[]) => {
    const [url, payload] = args;

    // 生成唯一的缓存键
    const cacheKey = `${url}-${JSON.stringify(payload)}`;

    // 检查是否有缓存
    if (cache.has(cacheKey)) {
      return Promise.resolve(cache.get(cacheKey));
    }

    // 检查是否有进行中的 Promise
    if (!cachePromise.has(cacheKey)) {
      cachePromise.set(
        cacheKey,
        originalFunction(...args)
          .then((result) => {
            // 请求完成后缓存结果
            cache.set(cacheKey, result);
            // 缓存过期后清除缓存,防止内存泄露
            setTimeout(() => {
              cache.delete(cacheKey);
            }, cacheDuration);
            
            return result;
          })
          .finally(() => {
            // 请求结束后清除 Promise 缓存
            cachePromise.delete(cacheKey);
          }),
      );
    }
    return cachePromise.get(cacheKey)!;
  };
};

Useage:

ts 复制代码
// 同时最多并发 5 个,每秒最多请求 9 个,5 秒内走缓存的请求
export const limitPost = 
requestCache(
  limitQps(
    limitConcurrency(
      request.post, 5), 
    9),
  5000,
);

总结

以上就是利用缓存,解决重复请求的思路和代码。还是有一些不完善的地方,比如:

  • 只针对 url 和 payload 进行了去重判定(因为实际项目中,通常只要 POST 请求需要控制);
  • 如果有些需要重试的逻辑,接口返回的是正常的 200,没有走 catch,而是走了 then。那么错误的结果也是会被缓存的,重试的逻辑会受影响;

这些问题都需要根据项目的实际情况来改良。大家领会这其中的思路即可,相信这里绝大多数的代码是可以复用的。希望大家在追求极致的路上稳步前进。

PS: 本文代码用了 ChatGPT 生成基础代码,然后笔者进行了优化,确实可以提效。

相关推荐
Flynt9 小时前
Cloudflare 用 Rust 重写了 TypeScript:我拿同一批代码跑了跑,报错一模一样,退出码不一样
rust·typescript·前端工程化
落花看风雪10 小时前
从零开始搭建后台系统(vue3+TypeScript+Vite)01
typescript·vue·js
行者全栈架构师14 小时前
我让 AI 独立开发了一款游戏:零依赖、188 个测试全绿、工期压到四分之一
typescript·canvas·vibecoding
Eyfcom15 小时前
〖深度〗鸿蒙 ArkTS 移动应用开发经验:从 TypeScript 心智模型到声明式运行时的六层认知重构
typescript·移动开发·harmonyos·arkts
zzu123zsw21 小时前
业务拆分后的治理深水区:解构企业级微服务六大核心设计模式与高可用落真实践
微服务·设计模式·架构·高可用·微服务架构设计
牧瀬クリスだ21 小时前
观察者模式:一个对象变化,一群对象自动响应
java·观察者模式·设计模式
拆房老料1 天前
【第四部分:Agentic 智能体设计模式】16. 一个 Prompt 搞不定复杂任务?试试 Prompt Chaining
java·前端·人工智能·设计模式·prompt
MobotStone1 天前
AI 写代码越来越爽,但程序员可能越来越废了
人工智能·设计模式
墨子夕1 天前
设计模式——Java × Python 双语言精讲 · 23 个经典模式 + 现代演进(前言篇)
java·开发语言·设计模式
墨子夕2 天前
设计模式——Java × Python 双语言精讲 · 23 个经典模式 + 现代演进(第一篇)
java·python·设计模式