【问题标题】:Is it good to call subscribe inside subscribe?在订阅中调用订阅是一种好方法吗?
【发布时间】:2019-02-18 09:53:47
【问题描述】:

我需要将三个数据从三个不同的 API 传递给一个函数:

this.service.service1().subscribe( res1 => {
  this.service.service1().subscribe( res2 => {
    this.service.service1().subscribe( res3 => {
      this.funcA(res1, res2, res3);
  });
  });
});

在订阅中订阅是一种好习惯吗?

【问题讨论】:

  • 这都是依赖调用吗?
  • 感谢您的快速回复。我能问你最好的方法吗?
  • 没有“伤害”,但这在 rxjs 中被认为是一种反模式。如果这三个调用是独立的,这会不必要地慢。
  • 我强烈反对@Faisal - 这根本不是一个好习惯。
  • @PankajParkar 不,他们不依赖,如果我进行独立调用,我无法将数据传递给 funcA。

标签: javascript functional-programming rxjs observable


【解决方案1】:

正确的方法是以某种方式组合各种 observables,然后订阅整个流程——如何组合它们将取决于您的确切要求。

如果你能同时完成所有这些:

forkJoin(
  this.service.service1(), this.service.service2(), this.service.service3()
).subscribe((res) => {
  this.funcA(res[0], res[1], res[2]);
});

如果每个都依赖于前一个的结果,可以使用mergeMap(以前称为flatMap)或switchMap:

this.service.service1().pipe(
  mergeMap((res1) => this.service.service2(res1)),
  mergeMap((res2) => this.service.service3(res2))
).subscribe((res3) => {
  // Do something with res3.
});

... 等等。有很多算子可以组成 observables 来覆盖很多不同的场景。

【讨论】:

  • forkJoin是否保证执行顺序?
  • 它会按照它们编码的顺序返回结果,但不知道它是否能保证按照指定的顺序启动它们。但是,由于它们是 HTTP 请求,即使它们以特定顺序启动,也无法保证服务器将接收和处理它们的顺序,除非您按顺序形成它 - 所以如果这很重要,您需要使用类似于 flatMap 链,您在开始下一个@PankajParkar 之前等待一个
  • 假设这些是 HTTP 请求,您将如何处理每种情况下的 HTTP 错误,尤其是在 flatMap 示例中?另外,如果我正在阅读以下内容,如果我们需要确保完整性的顺序,我们需要使用concatMap而不是flatMap:learnrxjs.io/operators/transformation/concatmap.html
  • concatMap 与 flatMap 将取决于您的用例 @AsGoodAsItGets - 两者都可以确保上述示例中的完成顺序。对于错误处理,在上面的 flatMap 示例中,如果(比如说)service2 出错,那么 service3 将不会被调用,它会转到传递给 subscribe 的错误处理函数(如果你传递了一个)。如果您想更早地捕获它们,可以在管道中使用 catchError...
  • 如果您想单独处理每个服务的错误怎么办? IE。服务 1 失败的一个错误处理程序,服务 2 的另一个错误处理程序,等等?无论如何,我无法想象订阅超过 2 个的实际情况,只是想知道它的结构。
【解决方案2】:

您可以使用forkJoin 将Observables 组合成单个值Observable

forkJoin(
  this.service.service1(),
  this.service.service2(),
  this.service.service3()
).pipe(
  map(([res1, res2, res3 ]) => {
    this.funcA(res1, res2, res3);
  })

【讨论】:

  • 我不认为它是等价的,因为你在这里调用它是并行的,而作者是顺序做的
【解决方案3】:

如果调用可以并行解决,您可以使用 forkJoin,如下所示:

joinedServiceCalls() {
   return forkJoin(this.service1(), this.service2(), this.service3());
}

然后订阅该方法。 https://www.learnrxjs.io/operators/combination/forkjoin.html

【讨论】:

    【解决方案4】:

    尽管上述所有帮助provide solutions to this particular problem 似乎都没有解决这里明显的潜在问题,特别是:

    在 subscribe 里面调用 subscribe 是不是好办法?

    tl;dr

    不,在订阅中调用订阅是不好的。

    为什么?

    因为这不是函数式编程的工作方式。你不是在功能上思考你在程序上的思考。这本身不一定是个问题,但使用 rxjs(和其他响应式编程扩展)的全部意义在于编写函数式代码。

    我不会详细介绍 functional programming is 的所有细节,但本质上,函数式编程的重点是将数据视为流。由函数操作并由订阅者使用的流。一旦您在另一个订阅中添加订阅,您的在消费者内部(而不是在流内部)操纵数据。所以你的功能流现在被打破了。这可以防止其他消费者在您的代码中进一步使用该流。所以你已经把你的功能流变成了一个过程。

    【讨论】:

      【解决方案5】:

      看起来很奇怪,我会走这条路,因为它看起来更干净:

      async myFunction () {
      //...
      const res1 = await this.service.service1().toPromise();
      const res2 = await this.service.service2().toPromise();
      const res3 = await this.service.service3().toPromise();
      this.funcA(res1, res2, res3);
      //...
      
      }
      

      编辑

      或并行进行

      async myFunction () {
      
      //...
      let res1;
      let res2;
      let res3;
      [res1,res2,res3] = await Promise.all([this.service.service1().toPromise(),
                                            this.service.service2().toPromise(),
                                            this.service.service3().toPromise()]);
      this.funcA(res1, res2, res3);
      //...
      
      }
      

      【讨论】:

      • 这遇到了同样的问题,即您按顺序而不是并行进行三个调用,这不必要地慢(假设它们可以并行运行)。
      • rxjs 方法的最佳实践答案不应该是“将所有内容转换为承诺”——它可能适用于某些场景(例如 HTTP 调用),但 Observables 的美妙之处在于它们适用于流而不是像 Promise 这样的单次响应 - 将它们转换为 Promise 会失去使用 observables 的全部意义。
      【解决方案6】:

      您可以使用zip RxJs operator,然后在这种情况下您将只使用一个订阅。

      然后您可以在该订阅中调用您的函数,因为所有结果都可用。

      Observable.zip(
        this.service.service1(),
        this.service.service1(),
        this.service.service1()
      ).subscribe([res1, res2, res3]) {
        this.funcA(res1, res2, res3);
      }
      

      【讨论】:

        【解决方案7】:

        如前所述,forkjoin 是一个很好的解决方案,但它只发出完成的调用。如果这些是要重复发出的值,请使用 I would combineLatest。

        【讨论】:

          猜你喜欢
          • 2020-10-26
          • 2021-01-24
          • 2021-04-02
          • 2020-04-20
          • 2018-09-07
          • 2018-01-01
          • 1970-01-01
          • 2021-01-28
          • 2021-06-14
          相关资源
          最近更新 更多