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

相关推荐
折哥的程序人生 · 物流技术专研1 分钟前
【电商多平台电子面单对接实战|第二篇】抖音代发电子面单对接:从“面条代码”到整洁架构的涅槃之路
设计模式·架构·系统架构·单元测试·代码规范·单一职责原则
云水一下10 分钟前
TypeScript 从零基础到精通(四):面向对象编程(类与继承)
javascript·typescript
CDN36020 分钟前
【架构进阶】告别配置漂移!用 NodeNext + Workspace 打造优雅的 TypeScript Monorepo
前端·javascript·typescript
葫芦和十三1 小时前
范式之变|Agent 设计,换语言了
人工智能·设计模式
颂love1 小时前
TypeScript速学
前端·javascript·typescript
ourenjiang1 小时前
【学习设计模式】原型模式
学习·设计模式·原型模式
贵慜_Derek1 小时前
《从零实现 Agent 系统》连载 20|MCP 与 Code Execution:协议、档位与 Sidecar
人工智能·设计模式·架构
wuhen_n1 小时前
RAG 入门:检索增强生成核心原理
前端·人工智能·typescript·langchain·ai编程
风吹夏回21 小时前
TypeScript 快速上手指南:从 JavaScript 到类型安全
javascript·ubuntu·typescript
Surprisec1 天前
如何用 TypeScript 写一个最小可运行的 CLI Agent
前端·人工智能·typescript