Angular拦截器刷新Token时SwitchMap未触发问题求助
问题:HTTP拦截器Token刷新流程失效,SwitchMap未触发
问题详情
在基于Angular的Ionic应用中实现了HTTP拦截器的Token刷新逻辑,但功能异常:switchMap完全未触发,控制台无法看到switchMap fired!日志,后续的重试请求也未发送。
相关代码实现
refreshTokens函数
async refreshTokens() { return new Promise((resolve, reject) => { this.dataService .post(this.apiUriService.refreshTokenUri(), { refresh_token: this.authData.refreshToken, }) .pipe(take(1)) .subscribe({ next: (res: any) => { if (!res) { return reject(null); } const resData = res.data ? res.data : null; this.utils.setItemOnLocalStorage( Constants.authDataStorageItemName, resData ); resolve(resData); }, error: (error) => { return reject(error); }, }); }); }
handle401Error函数
async handle401Error(req: HttpRequest<any>, next: HttpHandler, error: any) { return from(this.refreshTokens()).pipe( switchMap((res: any) => { console.log('switchMap fired!'); const request = this.addToken(req, res.access_token); return next.handle(request); }), catchError((error) => { this.logoutUser(); return throwError(error); }) ); }
完整拦截器类代码
import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse, } from '@angular/common/http'; import { Observable, throwError, BehaviorSubject, tap, take, from, switchMap, catchError, } from 'rxjs'; import { AuthService } from './auth/auth.service'; import { ApiUriService } from './api-uri.service'; import { DataService } from './data.service'; import { Constants } from '../constants/constants'; import { CommonUtilsService } from './common-utils.service'; import { UserService } from './user/user.service'; @Injectable() export class CommonHttpInterceptor implements HttpInterceptor { tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(''); get authData() { return this.authService.readUserInfoFromLocalStorage(); } constructor( private authService: AuthService, private router: Router, private apiUriService: ApiUriService, private dataService: DataService, private userService: UserService, private utils: CommonUtilsService ) {} intercept( request: HttpRequest<unknown>, next: HttpHandler ): Observable<HttpEvent<unknown>> { const userInfoData = this.authService.authUserData; const headers: any = {}; if (userInfoData && userInfoData.accessToken) { headers['Authorization'] = `Bearer ${userInfoData.accessToken}`; } const clonedRequest = request.clone({ setHeaders: headers }); return next.handle(clonedRequest).pipe( tap( (event: HttpEvent<any>) => { if (event instanceof HttpResponse) { // case when we need to transform the response } }, (error: HttpErrorResponse) => { // handle refresh token in case of the access token is expired if (error.status === 401) { return this.handle401Error(request, next); } return throwError(error); } ) ); } logoutUser() { this.authService.doLogout(); this.router.navigate(['auth/login']); } handle401Error(req: HttpRequest<any>, next: HttpHandler) { return from(this.refreshTokens()).pipe( switchMap((res: any) => { console.log('switchMap fired!'); const request = this.addToken(req, res.access_token); return next.handle(request); }), catchError((error) => { this.logoutUser(); return throwError(error); }) ); } // refresh access token OR logout user if request failed refreshTokens() { return new Promise((resolve, reject) => { this.dataService .post(this.apiUriService.refreshTokenUri(), { refresh_token: this.authData.refreshToken, }) .pipe(take(1)) .subscribe({ next: (res: any) => { if (!res) { return reject(null); } const resData = res.data ? res.data : null; this.utils.setItemOnLocalStorage( Constants.authDataStorageItemName, resData ); resolve(resData); }, error: (error) => { return reject(error); }, }); }); } //add token to authorization header private addToken(request: HttpRequest<any>, token: any) { const newToken = this.authService.shiftoCookieValue ? this.authService.shiftoCookieValue : token; if (newToken) { let clone: HttpRequest<any>; clone = request.clone({ setHeaders: { Authorization: `Bearer ${token}`, }, }); return clone; } return request; } }
问题原因及修复方案
核心问题1:tap操作符无法正确替换错误流
intercept方法中使用tap处理错误,但tap仅用于执行副作用,不会返回新的Observable替换原流。当捕获到401错误时,返回的handle401ErrorObservable根本没有被订阅执行,这是switchMap未触发的根本原因。
修复:用catchError替换tap处理401错误,确保正确返回刷新Token并重试请求的流。
核心问题2:Observable转Promise冗余且易出问题
refreshTokens将Observable封装成Promise完全没必要,直接使用RxJS操作符可避免异步转换的潜在问题,代码更简洁。
修复后的关键代码
修正后的intercept方法
intercept( request: HttpRequest<unknown>, next: HttpHandler ): Observable<HttpEvent<unknown>> { const userInfoData = this.authService.authUserData; const headers: any = {}; if (userInfoData && userInfoData.accessToken) { headers['Authorization'] = `Bearer ${userInfoData.accessToken}`; } const clonedRequest = request.clone({ setHeaders: headers }); return next.handle(clonedRequest).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401) { return this.handle401Error(request, next); } return throwError(() => error); }) ); }
修正后的refreshTokens方法(去掉Promise封装)
refreshTokens(): Observable<any> { return this.dataService .post(this.apiUriService.refreshTokenUri(), { refresh_token: this.authData.refreshToken, }) .pipe( take(1), tap((res: any) => { if (!res) { throw new Error('Refresh token response is empty'); } const resData = res.data ? res.data : null; this.utils.setItemOnLocalStorage( Constants.authDataStorageItemName, resData ); }) ); }
修正后的handle401Error方法(无需from转换)
handle401Error(req: HttpRequest<any>, next: HttpHandler) { return this.refreshTokens().pipe( switchMap((res: any) => { console.log('switchMap fired!'); const request = this.addToken(req, res.access_token); return next.handle(request); }), catchError((error) => { this.logoutUser(); return throwError(() => error); }) ); }
额外优化点
addToken方法中newToken变量定义后未使用,直接用了传入的token,可修正该逻辑避免混淆。- 处理并发请求:若多个请求同时触发401,可利用
tokenSubject控制并发,防止重复调用刷新Token接口。
内容的提问来源于stack exchange,提问作者nabilinfo
相关产品推荐
相关产品推荐

