You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular缓存并行请求:如何优化拦截器避免重复处理待处理请求?

解决HTTP拦截器并发请求重复发起的最优方案

这是HTTP缓存场景里非常典型的问题——你的当前逻辑只缓存了已经完成的响应,但当多个相同的GET请求同时发起时,第一个请求还处于pending状态(缓存里还没数据),后续请求就会重复发起。最优的解决思路是:把正在处理中的请求Observable也纳入缓存管理,让后续相同请求直接复用这个pending的Observable,从而实现并发请求的去重。

核心思路拆解

  1. 生成请求唯一标识:因为HttpRequest是引用类型,不能直接作为缓存的key,我们需要把请求的URL、参数、方法等特征序列化,生成一个唯一的字符串key,确保相同的请求会命中同一个缓存条目。
  2. 分层缓存:缓存不仅要存储已完成的HttpResponse,还要存储正在pending的Observable<HttpResponse>:
    • 如果缓存里是已完成的响应,直接返回该响应
    • 如果缓存里是正在处理的Observable,直接订阅它(避免重复发起请求)
    • 如果没有缓存,发起请求并先把pending的Observable存入缓存,请求完成后再更新缓存为响应对象

完整代码实现(以Angular拦截器为例)

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpResponse
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap, finalize } from 'rxjs/operators';

@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  // 缓存Map:key是请求唯一标识,value可以是已完成的响应或pending的Observable
  private cache = new Map<string, Observable<HttpResponse<any>> | HttpResponse<any>>();

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 非GET请求不缓存,直接放行
    if (req.method !== 'GET') {
      return next.handle(req);
    }

    // 生成请求的唯一标识key
    const cacheKey = this.generateRequestKey(req);
    const cachedEntry = this.cache.get(cacheKey);

    // 情况1:缓存中存在已完成的响应,直接返回克隆后的响应(避免修改原不可变对象)
    if (cachedEntry instanceof HttpResponse) {
      return of(cachedEntry.clone());
    }

    // 情况2:缓存中存在正在pending的请求Observable,直接复用它
    if (cachedEntry instanceof Observable) {
      return cachedEntry;
    }

    // 情况3:无缓存,发起请求并缓存pending的Observable
    const requestObservable = next.handle(req).pipe(
      // 请求成功完成后,将缓存更新为已完成的响应
      tap(event => {
        if (event instanceof HttpResponse) {
          this.cache.set(cacheKey, event.clone());
        }
      }),
      // 请求完成(无论成功/失败)后,移除pending的Observable(避免失败请求一直被缓存)
      finalize(() => {
        if (this.cache.get(cacheKey) === requestObservable) {
          this.cache.delete(cacheKey);
        }
      })
    );

    // 先将pending的Observable存入缓存
    this.cache.set(cacheKey, requestObservable);

    return requestObservable;
  }

  /**
   * 生成请求的唯一标识key
   * 基于请求的方法、URL和参数拼接,确保相同请求生成相同key
   */
  private generateRequestKey(req: HttpRequest<any>): string {
    return `${req.method}-${req.urlWithParams}`;
  }
}

关键细节说明

  • 请求唯一标识:urlWithParams已经包含了URL和查询参数,能准确区分不同的GET请求;如果是POST请求(虽然我们这里只处理GET),还需要加入请求体的哈希值。
  • 响应克隆:HttpResponse是Angular的不可变对象,返回时必须调用clone(),避免后续操作意外修改原响应。
  • 失败请求处理:通过finalize操作符,在请求完成(无论成功或失败)后移除缓存中的pending Observable,这样如果请求失败,后续请求不会一直拿到失败的Observable。
  • 可扩展性:你可以在此基础上添加缓存过期逻辑(比如用定时器自动删除旧缓存)、缓存清空方法(比如某些操作后需要清空特定缓存)等。

内容的提问来源于stack exchange,提问作者a bsss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:03:37