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

Angular HttpInterceptor中无需async/await等待Promise的实现方案

我明白你遇到的这个并发请求下Token刷新的时序问题——确实在Angular拦截器里直接用Promise的.then()很容易踩坑,毕竟拦截器要求返回Observable,而Promise的异步逻辑不会阻塞后续的return语句,导致旧Token还没被覆盖就发送了请求,触发403和误登出。

下面给你一套可行的解决方案,核心是用RxJS把Promise转换成Observable,同时处理并发请求的场景,避免重复刷新Token:

核心思路

  1. 用RxJS的from()方法把getNewToken()返回的Promise转换成Observable,这样就能用RxJS操作符控制异步流
  2. 用一个BehaviorSubject跟踪Token刷新状态,避免多个请求同时触发刷新,导致重复调用后端接口
  3. 确保所有需要等待新Token的请求,都在Token刷新完成后再用新Token重新发送

改造后的完整代码示例

import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpEvent,
  BehaviorSubject,
  throwError
} from '@angular/common/http';
import { Observable, from, switchMap, filter, take, catchError } from 'rxjs';
import { AuthService } from './auth.service';
import * as util from './jwt-utils';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
  // 用于跟踪Token是否正在刷新,避免并发请求重复触发刷新
  private isRefreshing$ = new BehaviorSubject<boolean>(false);

  constructor(private _authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 跳过刷新Token的请求,避免循环拦截
    if (req.headers.has("refreshTokenRequest")) {
      return next.handle(req).pipe(
        tap(() => console.log("Interceptor handling refresh token request:", req.url))
      );
    }

    const jwtToken = localStorage.getItem("jwt_token");
    if (jwtToken) {
      if (util.isJwtExpired("jwt_token")) {
        // 如果正在刷新Token,等待刷新完成后再处理当前请求
        if (this.isRefreshing$.value) {
          return this.isRefreshing$.pipe(
            filter(isRefreshing => !isRefreshing),
            take(1),
            switchMap(() => this.handleRequestWithUpdatedToken(req, next))
          );
        }

        this.isRefreshing$.next(true);
        // 将Promise转换为Observable,用RxJS操作符控制异步流
        return from(this.getNewToken()).pipe(
          switchMap((newTokenResponse) => {
            this._authService.setAuthorizationToken(newTokenResponse.token);
            console.log("New token has been set");
            this.isRefreshing$.next(false);
            // 克隆请求并更新Token头部,确保用新Token发送
            return this.handleRequestWithUpdatedToken(req, next);
          }),
          catchError((error) => {
            this.isRefreshing$.next(false);
            // 刷新失败,执行登出逻辑
            this._authService.logout();
            return throwError(() => error);
          })
        );
      }
    }

    // Token未过期或不存在,直接处理请求
    return this.handleRequestWithCatchError(req, next);
  }

  /**
   * 克隆请求并使用最新的Token更新请求头
   */
  private handleRequestWithUpdatedToken(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const updatedReq = this.addHeaders(req); // 这里的addHeaders方法需要读取最新的Token生成请求头
    return this.handleRequestWithCatchError(updatedReq, next);
  }

  /**
   * 原有的请求处理+错误捕获逻辑
   */
  private handleRequestWithCatchError(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      // 这里保留你原有的错误处理逻辑,比如捕获403等
      catchError(error => {
        // 你的错误处理代码
        return throwError(() => error);
      })
    );
  }

  /**
   * 原有的添加请求头方法
   */
  private addHeaders(req: HttpRequest<any>): HttpRequest<any> {
    const token = localStorage.getItem("jwt_token");
    if (token) {
      return req.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }
    return req;
  }

  /**
   * 原有的获取新Token方法(返回Promise)
   */
  private getNewToken(): Promise<any> {
    // 你的刷新Token逻辑,比如调用后端接口
    return fetch('/api/refresh-token', {
      method: 'POST',
      body: JSON.stringify({ refreshToken: localStorage.getItem("refresh_token") })
    }).then(res => res.json());
  }
}

关键细节说明

  1. Promise转Observable:用from(this.getNewToken())把Promise转换成Observable,这样就能用RxJS的switchMap、catchError等操作符控制异步流程,确保Token刷新完成后再处理后续请求。
  2. 并发请求控制:isRefreshing$这个BehaviorSubject会跟踪刷新状态,当多个请求同时发现Token过期时,只有第一个请求会触发刷新,其他请求会等待刷新完成后,用新Token重新发送。
  3. 请求克隆:刷新Token后必须克隆原请求并更新请求头,因为原请求的头是基于旧Token生成的,直接复用会导致403。

这样改造后,就能彻底解决你遇到的“新Token还没返回就发送请求”的问题,同时避免了async/await和Observable返回类型冲突的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:13:47