Angular10升级至15后登录时Interceptor持续触发catchError致页面卡顿
Angular 10 升级至 15 后登录环节异常问题修复
核心问题表现
JsogServiceInterceptor的catchError代码块持续触发,但相同逻辑在 Angular 10 环境下可正常运行AuthenticationGuard的checkConnect()方法在subscribe(data => {})处逻辑异常,请求反复进入 Interceptor 的if (req.responseType === 'json')分支,导致登录页面卡顿,替换为pipe(map())后问题依旧
问题根源分析
1. Interceptor 中的计数逻辑错误
finalize 操作符无论请求成功或失败都会执行,已经处理了 activeLogins 的递减操作,但 catchError 中又重复执行 activeLogins--,导致计数异常,进而触发 networkStatus 状态反复切换,引发额外请求逻辑。
同时,代码中直接调用静态方法 AuthenticationGuard.checkConnection() 是错误的——checkConnection 依赖 Guard 实例的 http、store 等服务,静态调用会导致依赖未初始化,触发新的错误。
2. Guard 中的订阅逻辑问题
store.select的订阅未取消,会导致内存泄漏,且每次调用checkConnect()都会新增订阅,重复发起 HTTP 请求- 嵌套
subscribe是 RxJS 反模式,会导致请求流无法有效控制,进而引发重复请求和页面卡顿
修复代码示例
修复后的 JsogServiceInterceptor
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; import {Injectable} from '@angular/core'; import {JsogService} from 'jsog-typescript'; import {catchError, finalize, map} from 'rxjs/operators'; import {throwError, Observable, BehaviorSubject} from "rxjs"; import {AuthenticationGuard} from './guard.service'; @Injectable({ providedIn: 'root' }) export class JsogServiceInterceptor implements HttpInterceptor { // 改用BehaviorSubject确保初始状态稳定 private networkStatus: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private activeLogins = 0; // 注入AuthenticationGuard实例,替代静态调用 constructor(private jsog: JsogService, private authGuard: AuthenticationGuard) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (req.responseType === 'json') { // 仅当无活跃请求时发送loading状态 if (this.activeLogins === 0) { this.networkStatus.next(true); } this.activeLogins++; return next.handle(req) .pipe( finalize(() => { this.activeLogins--; if (this.activeLogins === 0) { this.networkStatus.next(false); } }), map(event => { if (event instanceof HttpResponse) { const body: any = this.jsog.deserialize(event.body); return event.clone({ body }); } return event; }), catchError(err => { if (err instanceof HttpErrorResponse) { // 移除重复的计数操作,交给finalize统一处理 this.authGuard.checkConnect(); // Angular 13+推荐使用工厂函数返回错误 return throwError(() => err); } return throwError(() => err); }) ); } return next.handle(req); } }
修复后的 AuthenticationGuard
import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { BehaviorSubject, takeUntil, switchMap, tap, catchError, of } from 'rxjs'; import { Store, createFeatureSelector } from '@ngrx/store'; import { HttpClient } from '@angular/common/http'; import { environment } from '../environments/environment'; export interface UserInfo {} export interface AuthState { loggedIn: boolean } export enum RequestStatusEnum { OK } export interface RequestStatus { status: RequestStatusEnum } @Injectable({ providedIn: 'root' }) export class AuthenticationGuard implements CanActivate { private authenticationData: BehaviorSubject<UserInfo> = new BehaviorSubject<UserInfo>(undefined); private destroy$ = new BehaviorSubject<void>(void 0); // 用于清理订阅 constructor( private store: Store<{ authenticationData: AuthState }>, private http: HttpClient, private router: Router ) {} checkConnect(): void { this.store.select(createFeatureSelector<AuthState>('authenticationData')) .pipe( takeUntil(this.destroy$), // 组件销毁时自动取消订阅 switchMap(data => { if (!data.loggedIn) return of(null); // 未登录时跳过请求 // 用switchMap替换嵌套subscribe,控制请求流 return this.http.get<RequestStatus>(environment.systemServiceURL, { withCredentials: true, reportProgress: false }).pipe( tap(res => { if (res.status !== RequestStatusEnum.OK) { this.router.navigate(['/no-access']); } }), catchError(() => { this.store.dispatch({ type: 'AUTH_SET_LOGGED_OUT', // 补充明确的Action类型 payload: { loggedIn: false } }); return of(null); }) ); }) ) .subscribe(); } // 清理订阅,避免内存泄漏 ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } canActivate(): boolean | Observable<boolean> { // 补充canActivate核心逻辑(根据实际业务调整) return this.store.select(createFeatureSelector<AuthState>('authenticationData')) .pipe(tap(data => { if (!data.loggedIn) this.router.navigate(['/login']); })); } }
额外注意事项
- Angular 13+ 中
throwError推荐使用工厂函数形式,避免直接传递错误对象导致的潜在问题 - NGRX 的 Action 必须包含明确的
type属性,否则状态更新会异常 - 若仍存在重复请求,可在 HTTP 请求管道中添加
take(1)操作符,确保只取一次最新值
内容的提问来源于stack exchange,提问作者davidArchA
相关产品推荐
相关产品推荐

