前端大并发除了频控和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 生成基础代码,然后笔者进行了优化,确实可以提效。

相关推荐
葬送的代码人生11 小时前
别再让 AI 瞎写代码了!Vibe Coding 三步法教你写出靠谱代码
前端·设计模式·架构
无风听海14 小时前
Claude Agent Skills 的四种设计模式;从渐进式披露到最小权限
java·算法·设计模式
富贵冼中求15 小时前
从单向流到双向 RPC:Agent 通信协议的范式分叉与 ACP 协议实战拆解
设计模式·架构
晓说前端15 小时前
TypeScript 核心语法进阶 —— 字面量类型与类型推论
前端·typescript
电子科技圈18 小时前
先进封装、芯粒架构和3D集成——先进异构集成亟需兼具标准化与定制化能力的互联及总线IP解决方案
tcp/ip·设计模式·架构·软件构建·代码规范·设计规范
布兰妮甜19 小时前
从 0 搭建企业级 Vue3/Vite 脚手架(规范、eslint、husky、打包、环境变量全流程)
typescript·vue3·vite·脚手架·前端工程化
zjun100120 小时前
C++:2.工厂模式
设计模式
东方小月20 小时前
从0开发一个 Coding Agent(一):前言
前端·人工智能·typescript
触底反弹21 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
gis开发之家2 天前
《Vue3 从入门到大神35篇》Vue3 源码详解(五):effect 依赖收集原理——track 与 trigger 是如何工作的?
javascript·typescript·前端框架·vue3·vue3源码