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

如何在Angular5拦截器中用RxJS实现HttpRequest请求队列?

我之前在Angular 5项目里踩过一模一样的坑——并发请求碰到token过期时,一堆请求同时触发401,各自去刷新token,导致服务器收到好几个刷新请求,直到其中一个成功后剩下的才能重试。核心解决思路就是统一管控token刷新状态,让所有待重试的请求共享同一个刷新结果,下面是我验证过的完整实现方案:

拦截器核心实现

首先我们需要在拦截器里维护两个关键状态:一个标记当前是否正在刷新token的布尔值,一个用来广播新token的BehaviorSubject,让所有等待的请求订阅它。

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { catchError, filter, take, switchMap } from 'rxjs/operators';
import { AuthService } from './auth.service'; // 替换成你自己的Auth服务

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  // 标记是否正在刷新token
  private isRefreshing = false;
  // 用来传递刷新后的新token的Subject
  private refreshTokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null);

  constructor(private authService: AuthService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    // 跳过刷新token的请求,避免拦截死循环
    if (request.url.includes('/api/auth/refresh-token')) {
      return next.handle(request);
    }

    // 给请求添加Authorization头
    request = this.addAuthHeader(request);

    return next.handle(request).pipe(
      catchError((error) => {
        // 只处理401未授权的异常
        if (error instanceof HttpErrorResponse && error.status === 401) {
          return this.handleUnauthorized(request, next);
        }
        // 其他错误直接抛出
        return throwError(error);
      })
    );
  }

  // 给请求添加Authorization头
  private addAuthHeader(request: HttpRequest<any>): HttpRequest<any> {
    const accessToken = this.authService.getAccessToken(); // 从本地获取当前token
    if (accessToken) {
      return request.clone({
        setHeaders: {
          Authorization: `Bearer ${accessToken}`
        }
      });
    }
    return request;
  }

  // 处理401未授权逻辑
  private handleUnauthorized(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    // 如果当前没有在刷新token,发起刷新请求
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null); // 先发送null,标记等待状态

      return this.authService.refreshToken().pipe(
        switchMap((newToken: string) => {
          this.isRefreshing = false;
          this.refreshTokenSubject.next(newToken); // 广播新token
          // 用新token重试当前请求
          return next.handle(this.addAuthHeader(request));
        }),
        catchError((error) => {
          this.isRefreshing = false;
          // 刷新失败(比如refresh token也过期),直接跳登录
          this.authService.logout();
          return throwError(error);
        })
      );
    } else {
      // 如果已经在刷新token,等待新token返回后重试请求
      return this.refreshTokenSubject.pipe(
        filter(token => token !== null), // 过滤掉初始的null值
        take(1), // 只取第一个有效token
        switchMap(token => {
          return next.handle(this.addAuthHeader(request));
        })
      );
    }
  }
}

AuthService配套方法

你的AuthService需要提供获取token、刷新token、保存token和登出的方法,示例如下:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly STORAGE_KEY_TOKEN = 'access_token';
  private readonly STORAGE_KEY_REFRESH_TOKEN = 'refresh_token';

  constructor(private http: HttpClient) {}

  // 获取当前access token
  getAccessToken(): string | null {
    return localStorage.getItem(this.STORAGE_KEY_TOKEN);
  }

  // 获取refresh token
  private getRefreshToken(): string | null {
    return localStorage.getItem(this.STORAGE_KEY_REFRESH_TOKEN);
  }

  // 刷新token接口
  refreshToken(): Observable<string> {
    const refreshToken = this.getRefreshToken();
    return this.http.post<any>('/api/auth/refresh-token', { refreshToken }).pipe(
      map(response => {
        const newAccessToken = response.access_token;
        // 保存新的access token到本地
        localStorage.setItem(this.STORAGE_KEY_TOKEN, newAccessToken);
        return newAccessToken;
      })
    );
  }

  // 登出逻辑
  logout(): void {
    localStorage.removeItem(this.STORAGE_KEY_TOKEN);
    localStorage.removeItem(this.STORAGE_KEY_REFRESH_TOKEN);
    // 这里可以跳转到登录页
    window.location.href = '/login';
  }
}

关键细节说明

  • isRefreshing:这个布尔值是核心锁,确保同一时间只有一个刷新token的请求发送到服务器,避免重复刷新。
  • refreshTokenSubject:作为所有等待请求的消息通道,当刷新成功后,新token会被广播给所有订阅它的请求,这些请求会立即用新token重试。
  • 跳过刷新token的请求:必须排除刷新token本身的请求,否则会进入拦截死循环。

最后一步:注册拦截器

别忘了在你的AppModule里注册这个拦截器,让Angular生效:

import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
  ...
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true // 允许多个拦截器
    }
  ]
})
export class AppModule {}

这个方案能完美解决你遇到的问题:当多个并发请求触发401时,只有第一个请求会发起token刷新,其他请求都会进入等待状态,直到新token返回后,所有等待的请求都会用新token重试,不会再出现多个未授权请求和重复刷新的情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:33:22