You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    )
}

问题原因及解决方案

核心原因

  1. RxJS版本适配问题:Angular 14对应RxJS 7版本,原轮询函数中takeWhile与interval的组合逻辑存在时序缺陷——takeWhile位于switchMap之前,导致轮询请求的发起依赖interval触发,外部变量shouldPoll的异步修改无法同步控制当前轮询流程,容易让Resolver误判Observable已完成。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 14:32:03