【问题标题】:How to execute an HTTP request in the background with RxJS如何使用 RxJS 在后台执行 HTTP 请求
【发布时间】:2020-02-23 10:59:13
【问题描述】:

我有一个包含 2 个步骤的 Angular 向导组件。在第一步中,向用户显示一个表单。在第二步中,另一个表单显示为长时间运行的 HTTP GET 的结果。目前这是可行的,当用户到达第二步时,会发出请求,最终用户会看到表单是长时间运行的调用的结果;但是,用户等待请求结束很烦人。

为了优化,我尝试在向导的第一步在后台触发长时间运行的请求,从而减少用户的等待时间。从技术上讲这是可能的,因为请求参数不依赖于步骤 1 中的用户操作。

然而,困难在于 Observables 什么都不做,除非你订阅它们。但是当我订阅 Observable 请求时,它将在向导步骤 1 中等待它完成(因此不会为用户带来性能提升)。

所以我需要以某种方式订阅可观察的请求,但在后台这样不会阻塞 begin 管道。

每个向导步骤都有一个onStepBeginonStepFinish。在第 1 步的onStepBegin 中,我想立即创建 observable 并执行请求。然后在第2步的onStepBegin中,应该等待请求的剩余时间。

从技术上讲,下面的代码可以工作,但请求不会早于步骤 2 被触发。我怎样才能让它在步骤 1 中立即触发请求?

我尝试了以下方法来修复它:

  • 添加一个发布操作符使 observable 变热:没有帮助,因为它将在 订阅后变热
  • 使用手动subscribe() 电话订阅;但是所谓的嵌套订阅似乎会造成内存泄漏

向导步骤 1:

public onStepBegin(begin: Observable<any>): Observable<MyContext> {
    return begin.pipe(
        tap(() => {
             ..
            this.context.loadInitial = this.loadInitialConfigurations(this.selectedElementclass.id).pipe(
                first(),
                publish()
            );
        })
    );
}

向导步骤 2:

public onStepBegin(begin: Observable<any>): Observable<MyContext> {
    return begin.pipe(
        switchMap(() => this.context.loadInitial),
        tap((c) => {
            this.context.configurations = c;
        }),
    );

【问题讨论】:

  • 您是否尝试过将请求的生成与公开结果的 observable 分开?例如。在this blog post 中,我展示了如何拥有一个触发请求(fetchRandomUser)的方法,该方法与结果的可观察(randomUser$)分开。在您的情况下,步骤 1 将立即调用触发方法,这会导致 service 订阅请求,步骤 2 将订阅结果。
  • 感谢您的评论;我认为在这种情况下需要手动订阅,这并不是最好的解决方案,而且我害怕引入内存泄漏。如果我手动订阅,之后我将无法清理订阅。
  • 清理什么?收到响应后,可观察到的请求完成,因此服务中的订阅不会永远挂起。
  • 我认为你是对的,谢谢

标签: angular http rxjs


【解决方案1】:

根据 Jon 对您的问题的评论,您需要在启动第 1 步的同时启动对第 2 步数据的 HTTP 请求。

当返回第 2 步数据的 HTTP 响应时,您可以更新已创建的主题。

当需要转到第 2 步时,您现在订阅了该主题。

如果 HTTP 响应已经返回,那么您已经有了数据。如果 HTTP 请求仍在处理中,则在响应返回后将开始第 2 步。

private step2data$: Subject<any> = new ReplaySubject<any>(1);

ngOnInit(): void {
  // kick off step one - however you do that
  this.beginStepOne().subscribe();

  // get step 2 data - however you do that
  this.http.get('http://mydatasource.com').subscribe(data => {
    this.step2data$.next(data);
  });
}

ngOnDestroy(): void {
  // tidy up
  this.step2data$.complete();
}

private beginStepOne(): Observable<MyContext> {
  // TODO: implement
}

// called when moving from step 1 to 2 - however you do that
private beginStepTwo(): Observable<MyContext> {
  // get data immediately if present, or wait for ongoing request to come back
  return this.step2data$.pipe(
    // we only need to take the first - like a normal http request
    take(1),
    // now switch to whatever method you use to kick off step 2
    switchMap(data => /* TODO: implement  */)
  );
}

【讨论】:

  • 这行得通,谢谢!顺便说一句,我使用 first() 而不是 take(1)
  • 酷。我通常更喜欢take(1) 以获得额外的安全性,但在这里使用first 应该没有问题。
猜你喜欢
  • 1970-01-01
  • 2020-11-23
  • 1970-01-01
  • 2017-06-18
  • 1970-01-01
  • 2012-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多