如何解决BehaviorSubject与异步函数的竞态条件及Angular令牌重复刷新问题
解决Angular拦截器并发请求重复刷新令牌问题
核心思路是共享令牌刷新请求的Observable,让所有并发请求复用同一个刷新流程,避免重复调用刷新接口。
1. 改造AuthService,添加共享刷新逻辑
在AuthService中维护一个用于跟踪当前刷新请求的变量,确保同一时间只有一个刷新请求发出:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, Observable, shareReplay, tap, of, catchError, map } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { private refreshTokenRequest$: Observable<any> | null = null; private tokenSubject = new BehaviorSubject<string | null>(null); constructor(private http: HttpClient) {} // 获取授权头的方法,处理并发刷新 headers(): Observable<AuthorizationHeaders> { // 检查是否已有正在进行的刷新请求 if (this.refreshTokenRequest$) { // 复用已有的刷新请求 return this.refreshTokenRequest$.pipe( map(() => this.buildAuthorizationHeaders()) ); } // 检查令牌是否过期,实现你的判断逻辑 if (this.isTokenExpired()) { // 发起新的刷新请求,并共享结果 this.refreshTokenRequest$ = this.http.post('/api/refresh-token', {}).pipe( tap((newToken) => { this.tokenSubject.next(newToken); // 刷新完成后清空请求引用 this.refreshTokenRequest$ = null; }), shareReplay(1), // 共享结果给后续订阅者 catchError((err) => { // 出错时也要清空请求引用,避免后续请求被阻塞 this.refreshTokenRequest$ = null; throw err; }) ); return this.refreshTokenRequest$.pipe( map(() => this.buildAuthorizationHeaders()) ); } // 令牌未过期,直接返回当前头 return of(this.buildAuthorizationHeaders()); } private isTokenExpired(): boolean { // 实现你的令牌过期判断逻辑 const token = this.tokenSubject.value; // ...自定义判断逻辑 return false; } private buildAuthorizationHeaders(): AuthorizationHeaders { const token = this.tokenSubject.value; return token ? { Authorization: `Bearer ${token}` } : {}; } } type AuthorizationHeaders = { [key: string]: string };
2. 简化拦截器代码
拦截器无需处理异步等待的并发问题,直接订阅AuthService的headers方法即可:
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { return this.authService.headers().pipe( switchMap((headers) => next.handle(request.clone({ setHeaders: headers })) ) ); }
关键逻辑说明
refreshTokenRequest$:存储当前正在进行的刷新请求Observable,确保并发请求复用同一个请求。shareReplay(1):让所有后续订阅者共享同一个刷新请求的结果,彻底避免重复发起请求。- 刷新完成/出错时清空
refreshTokenRequest$:确保下一次需要刷新时能发起新的请求,不会被旧请求阻塞。
内容的提问来源于stack exchange,提问作者patrick
相关产品推荐
相关产品推荐

