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

我还在学习 observables,所以如果有一个简单的解决方案,我不会感到惊讶。基本上我现在拥有的是四个嵌套订阅,第二个订阅()中有一个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)
    )
  ),
);
stack overflow How can I use switchMap instead of nested subscriptions?
原文答案
author avatar

接受的答案

mergeMap 而不是嵌套的 subscribe

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


快速搁置:

如果您订阅的 observable 发出一次并完成(如 http 请求), switchMapmergeMap 会产生相同的输出。在这些情况下,通常建议使用 switchMap 而不是 mergeMap 。原因从调试内存泄漏到边际性能,再到其他开发人员的期望。

为简单起见,我在这里忽略了这一点,在所有情况下都使用了 mergeMap


您可以通过嵌套 mergeMap 和/或嵌套 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);

答案: