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

Angular拦截器缓存问题:并发重复API请求未正确处理

Angular 并发请求缓存拦截器修复方案

你的拦截器在多组件并发请求场景失效的核心原因是:第一个请求的加载状态还未写入缓存,后续请求就已完成缓存检查并发起了重复请求。此外代码还存在缓存有效期判断逻辑颠倒、轮询监听状态效率低下的问题,以下是修复方案:

修复后的拦截器代码

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

interface CacheEntry {
  response: HttpResponse<unknown> | null;
  timestamp: number;
  request$: Observable<HttpEvent<unknown>> | null;
}

@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  private cache = new Map<string, CacheEntry>();
  private readonly CACHE_TTL = 2000; // 缓存有效期:2秒

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

    // 处理缓存重置请求头
    if (req.headers.get('reset')) {
      this.cache.delete(req.urlWithParams);
      // 移除reset头后发起请求,避免影响后续逻辑
      return next.handle(req.clone({ headers: req.headers.delete('reset') }));
    }

    const cacheKey = req.urlWithParams;
    const cachedEntry = this.cache.get(cacheKey);

    // 缓存存在且未过期,直接返回缓存响应
    if (cachedEntry?.response && Date.now() - cachedEntry.timestamp < this.CACHE_TTL) {
      return of(cachedEntry.response.clone());
    }

    // 存在正在进行的请求,直接复用该请求流
    if (cachedEntry?.request$) {
      return cachedEntry.request$;
    }

    // 无有效缓存,发起新请求并缓存请求流
    const request$ = next.handle(req).pipe(
      shareReplay(1), // 共享请求结果,让所有并发订阅者复用同一响应
      tap(event => {
        if (event instanceof HttpResponse) {
          // 请求成功,更新缓存为响应结果
          this.cache.set(cacheKey, {
            response: event.clone(),
            timestamp: Date.now(),
            request$: null
          });
        }
      }),
      finalize(() => {
        // 请求完成(成功/失败)后,清理缓存中的请求流
        const entry = this.cache.get(cacheKey);
        if (entry?.request$) {
          this.cache.delete(cacheKey);
        }
      })
    );

    // 先缓存请求流,确保后续并发请求能获取到
    this.cache.set(cacheKey, {
      response: null,
      timestamp: Date.now(),
      request$
    });

    return request$;
  }
}

关键修改说明

  1. 缓存结构升级:新增request$字段存储正在进行的请求Observable,并发请求进来时直接订阅该流,彻底避免重复发起API请求。
  2. 有效期逻辑修正:将原颠倒的过期判断改为Date.now() - cachedEntry.timestamp < this.CACHE_TTL,确保只有未过期的缓存才会被复用。
  3. 用shareReplay替代轮询:通过RxJS的shareReplay操作符共享请求结果,无需轮询监听状态,性能更优且逻辑更简洁。
  4. 请求失败清理:使用finalize在请求结束后清理缓存中的请求流,避免失败请求的无效缓存残留。
  5. 重置请求头优化:删除缓存后克隆请求并移除reset头,避免头信息污染后续请求处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 17:05:00