在订阅中调用订阅是一种好方法吗?
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】:
您可以使用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。
【讨论】:
以上是关于在订阅中调用订阅是一种好方法吗?的主要内容,如果未能解决你的问题,请参考以下文章