Angular升级至14后Resolver与RxJS SwitchMap协同异常问题
Angular 14 Resolver 不再等待轮询完成的问题排查
问题描述
我有一个供用户选择报告类型生成的组件,点击后通过router跳转到对应路由,该路由的组件依赖Resolver加载报告数据。在Angular 9版本中,Resolver会等待RxJS SwitchMap及轮询函数完成后返回生成好的报告;但升级至Angular 14后,Resolver不再等待轮询完成,发起启动报告的首个请求后立即加载页面。相同代码在不使用Resolver的场景下可正常运行,请问问题原因是什么?
核心代码
组件生成报告函数
generateReport(reportType, projectReport) { this.showSpinner = true; if(reportType === 'project-weekly-report') this.router.navigate(['/weeklyReport', projectReport]).catch<any>(this.handleError.bind(this)); else this.router.navigate(['/monthlyReport']).catch<any>(this.handleError.bind(this)); }
组件初始化逻辑
ngOnInit(): void { this.report = this.route.snapshot.data['projectWeeklyReport'].result; }
Resolver实现
@Injectable() export class ProjectWeeklyReportResolve implements Resolve<any> { constructor(private reportService: ReportService, private http: HttpClient) {} resolve(route: ActivatedRouteSnapshot) { return this.reportService.getProjectWeeklyReport(route.paramMap.get('projectName')); } }
服务获取报告函数
getProjectWeeklyReport(projectName) { var workerUrl = this.reportUrl + '/weeklyreport/projectname/' + projectName + '/job/'; return this.http .get<any>(this.reportUrl + '/weeklyreport/projectname/' + projectName, this.getAuthOptions(true)) .pipe( switchMap(workerId => this.pollFor(workerId, isWorkerCompleted, isWorkerFailed, 2000, workerUrl)), catchError(this.handleError) ); }
轮询函数实现
type CustomPollOperator = (data: any, condComp: (d) => boolean, condFail: (d) => boolean, ms: number, url: string) => Observable<any> const isWorkerCompleted = w => w.result; const isWorkerFailed = w => w.failedReason; //Convenience method for polling operation pollFor: CustomPollOperator = (data, condComp, condFail, ms, url) => { let shouldPoll = true; return interval(ms) .pipe( tap(() => console.warn('polling', shouldPoll)), takeWhile(() => shouldPoll), switchMap(() => this.http.get<any>(url + data.id, this.getAuthOptions(true))), tap(res => { if(condComp(res)) { shouldPoll = false; } else if(condFail(res)) { shouldPoll = false; throw new Error('Polling worker job failed'); } }), catchError(this.handleError) ) }
问题原因及解决方案
核心原因
- RxJS版本适配问题:Angular 14对应RxJS 7版本,原轮询函数中
takeWhile与interval的组合逻辑存在时序缺陷——takeWhile位于switchMap之前,导致轮询请求的发起依赖interval触发,外部变量shouldPoll的异步修改无法同步控制当前轮询流程,容易让Resolver误判Observable已完成。 - Resolver处理逻辑变更:Angular 14内部使用
lastValueFrom处理Resolver返回的Observable,原轮询函数会多次发出值(每次轮询结果),且完成时机依赖外部变量,导致Resolver无法正确识别最终完成信号,提前触发页面导航。
解决方案
方案1:重构轮询函数,确保只发出最终结果
修改轮询逻辑,持续请求直到满足完成条件,仅发出最终结果并立即完成Observable:
pollFor: CustomPollOperator = (data, condComp, condFail, ms, url) => { return interval(ms) .pipe( switchMap(() => this.http.get<any>(url + data.id, this.getAuthOptions(true))), // 先校验失败条件,触发错误终止轮询 tap(res => { if(condFail(res)) { throw new Error('Polling worker job failed'); } }), // 过滤出完成状态的结果 filter(res => condComp(res)), // 仅取第一个完成结果,自动完成Observable first(), catchError(this.handleError) ) }
方案2:在Resolver中显式等待最终结果
修改Resolver的resolve函数,用lastValueFrom显式将Observable转为Promise,确保等待到最后一个值:
import { lastValueFrom } from 'rxjs'; @Injectable() export class ProjectWeeklyReportResolve implements Resolve<any> { constructor(private reportService: ReportService, private http: HttpClient) {} async resolve(route: ActivatedRouteSnapshot) { return lastValueFrom(this.reportService.getProjectWeeklyReport(route.paramMap.get('projectName'))); } }
方案3:优化原轮询的变量控制逻辑
保留原轮询流程,将外部变量改为内部状态同步控制,确保Resolver能捕获到最终完成信号:
pollFor: CustomPollOperator = (data, condComp, condFail, ms, url) => { let completed = false; let failed = false; return interval(ms) .pipe( takeWhile(() => !completed && !failed), switchMap(() => this.http.get<any>(url + data.id, this.getAuthOptions(true))), tap(res => { if(condComp(res)) { completed = true; } else if(condFail(res)) { failed = true; throw new Error('Polling worker job failed'); } }), // 确保发出最后一次有效结果 takeLast(1), catchError(this.handleError) ) }
内容的提问来源于stack exchange,提问作者Tom O'Brien
相关产品推荐
相关产品推荐

