在订阅中调用订阅是一种好方法吗?

Posted

技术标签:

【中文标题】在订阅中调用订阅是一种好方法吗?【英文标题】: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。 【参考方案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】:

您可以使用forkJoinObservables 组合成单个值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。

【讨论】:

以上是关于在订阅中调用订阅是一种好方法吗?的主要内容,如果未能解决你的问题,请参考以下文章

单元测试:在设置方法中有断言是一种好习惯吗?

编写返回 void 的方法是一种好习惯吗?

在 async/await 之后使用 .then 是一种好方法吗?

在 Java 中更改参数是一种好习惯吗

将函数放在 useEffect 中是一种好习惯吗?

使用 OSGI 拆分应用程序层是一种好方法吗?