如何使用 switchMap 而不是嵌套订阅?

How can I use switchMap instead of nested subscriptions?

我仍在学习 observables,所以如果有一个简单的解决方案,我不会感到惊讶。基本上我现在拥有的是四个嵌套订阅,在第二个 subscribe() 中有一个 fourEach。我看到很多使用 switchMap 的答案,但我找不到也有 for 循环迭代的答案。我知道我可能应该使用嵌套订阅,但我不知道如何使用 forEach 来实现。

这是嵌套订阅的工作代码:

dialogRef.afterClosed().subscribe(result => {
  if(result) {
    this.createLikertResponseGroup(result.likertResponseGroup)
      .subscribe(likertResponseGroupJSON => {

        result.likertResponse.controls.likertResponseFormArray.controls.forEach((element) => {
          let characteristic = element.controls.characteristic;
          this.newResponseGroupId = likertResponseGroupJSON.LikertResponseGroup.id;

          this.createLikertResponse(element, this.newResponseGroupId)
            .subscribe(likertResponseJSON => {

              if (characteristic) {
                let responseId = likertResponseJSON.LikertResponse.id;

                this.createCharacteristic(characteristic, this.newResponseGroupId, responseId)
                  .subscribe(characteristicJSON => {
                    this.newCharacteristicId = characteristicJSON.Characteristic.id;
                  });
              }
            });
        });
      })
  }
});

我现在的工作。所以我的问题是,是否值得改变我这样做的方式?如果是这样,我该怎么做?

我还没走多远,但我对 switchMap 的尝试是这样的:

dialogRef.afterClosed().pipe(
  filter(result => result != null),
  switchMap(result => 
    from(result.likertResponse.controls.likertResponseFormArray.controls).pipe(
      // not sure what to do after this (or if I'm even doing it right)
    )
  ),
);

mergeMap 而不是嵌套 subscribe

mergeMap 执行嵌套订阅所做的所有事情,但它还允许您在发出订阅值时继续您的逻辑。


快说:

In cases where your subscribed observable emits once and completes (like an http request), switchMap and mergeMap produce the same output. switchMap is often recommended over mergeMap in these cases. The reasons range from debugging memory leaks, to marginal performance, to what other developers expect.

For simplicity's sake, I've ignored that here and used mergeMap in all cases.


您可以通过嵌套 mergeMap and/or 嵌套 subscriptions 来隐藏一些复杂性,因为您可以依靠功能闭包来设置和记住管道中较早的值。

它也可能会引起很大的混乱。众所周知,深度嵌套的函数在 JS 中难以调试,因此映射到中间对象以保存下一步所需值的额外努力(而不是通过函数闭包嵌套和获取中间值)是非常值得的。

它也稍微快一些,因为不需要运行时在调用堆栈中向上移动以查找变量(但同样,您应该这样做,因为它更清洁、可维护和可扩展,而不是为了尽早优化)。

这是您使用 mergeMap 和包含中间值的对象随意重写的代码:

dialogRef.afterClosed().pipe(
  filter(result => result), // <-- only "truthy" results pass same as if(result)
  mergeMap(result =>
    this.createLikertResponseGroup(result.likertResponseGroup).pipe(
      map(likertResponseGroupJSON => ({result, likertResponseGroupJSON}))
    )
  ),
  mergeMap(({result, likertResponseGroupJSON}) => merge(
    ...result.likertResponse.controls.likertResponseFormArray.controls.map(
      element => this.createLikertResponse(
        element, 
        likertResponseGroupJSON.LikertResponseGroup.id
      ).pipe(
        map(likertResponseJSON => ({
          likertResponseJSON,
          characteristic: element.controls.characteristic,
          newResponseGroupId: likertResponseGroupJSON.LikertResponseGroup.id
        }))
      )
    )
  )),
  filter(({characteristic}) => characteristic) // only "Truthy" characteristic allowed
  mergeMap(({likertResponseJSON, characteristic, newResponseGroupId}) =>
    this.createCharacteristic(
      characteristic, 
      newResponseGroupId, 
      likertResponseJSON.LikertResponse.id
    ).pipe(
      map(characteristicJSON => ({
        newCharacteristicId: characteristicJSON.Characteristic.id,
        newResponseGroupId
      }))
    )
  )
).subscribe(({newCharacteristicId, newResponseGroupId}) => {
  this.newResponseGroupId = newResponseGroupId;
  this.newCharacteristicId = newCharacteristicId;
});

merge/forkJoin/concat 而不是 forEach(stream.subscribe())

您会在上面的代码中注意到,当需要重新编写您的 forEach 循环时,我使用了 mergeArray#map 的组合,而不是 Array#forEach

merge 等价于 forEach(stream.subscribe()),但其他人可以改变行为,甚至可以提高性能或让您直观地组合更复杂的流。

这里,第 2 行和第 3 行的输出相同。然而,第二个很容易用更多的 RxJS 运算符扩展

1. const arrayOfStreams = [s1,s2,s3,s4];
2. arrayOfStreams.forEach(s => s.subscribe(console.log));
3. merge(...arrayOfStreams).subscribe(console.log);

扩展:

arrayOfStreams.forEach(s => s.subscribe(value => {
  if(this.isGoodValue(value)){
    console.log(value.append(" end"))
  }
}));

merge(...arrayOfStreams).pipe(
  filter(this.isGoodValue),
  map(value => value.append(" end"))
).subscribe(console.log);