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

相关推荐
Engineer邓祥浩22 分钟前
设计模式学习(14) 23-12 代理模式
学习·设计模式·代理模式
小码过河.40 分钟前
设计模式——单例模式
单例模式·设计模式
dxnb2243 分钟前
Datawhale26年1月组队学习:Agentic AI+Task2反思设计模式
学习·设计模式
老蒋每日coding1 小时前
AI智能体设计模式系列(八)—— 记忆管理模式
人工智能·设计模式·golang
Mr YiRan1 小时前
重学设计模式之拦截器和责任链
设计模式
VT.馒头1 小时前
【力扣】2637. 有时间限制的 Promise 对象
前端·javascript·leetcode·typescript
小码过河.1 小时前
设计模式——享元模式
java·设计模式·享元模式
VT.馒头2 小时前
【力扣】2631. 分组
javascript·算法·leetcode·typescript
yyt36304584114 小时前
TypeScript { [key: string]: unknown } 索引签名写法和 Record 替代
前端·javascript·vue.js·typescript·ecmascript·es6
前端小趴菜0515 小时前
TypeScript
前端·typescript