Angular HttpInterceptor中无需async/await等待Promise的实现方案
我明白你遇到的这个并发请求下Token刷新的时序问题——确实在Angular拦截器里直接用Promise的.then()很容易踩坑,毕竟拦截器要求返回Observable,而Promise的异步逻辑不会阻塞后续的return语句,导致旧Token还没被覆盖就发送了请求,触发403和误登出。
下面给你一套可行的解决方案,核心是用RxJS把Promise转换成Observable,同时处理并发请求的场景,避免重复刷新Token:
核心思路
- 用RxJS的
from()方法把getNewToken()返回的Promise转换成Observable,这样就能用RxJS操作符控制异步流 - 用一个
BehaviorSubject跟踪Token刷新状态,避免多个请求同时触发刷新,导致重复调用后端接口 - 确保所有需要等待新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()); } }
关键细节说明
- Promise转Observable:用
from(this.getNewToken())把Promise转换成Observable,这样就能用RxJS的switchMap、catchError等操作符控制异步流程,确保Token刷新完成后再处理后续请求。 - 并发请求控制:
isRefreshing$这个BehaviorSubject会跟踪刷新状态,当多个请求同时发现Token过期时,只有第一个请求会触发刷新,其他请求会等待刷新完成后,用新Token重新发送。 - 请求克隆:刷新Token后必须克隆原请求并更新请求头,因为原请求的头是基于旧Token生成的,直接复用会导致403。
这样改造后,就能彻底解决你遇到的“新Token还没返回就发送请求”的问题,同时避免了async/await和Observable返回类型冲突的问题。
内容的提问来源于stack exchange,提问作者Jake12342134
相关产品推荐
相关产品推荐

