【问题标题】:Is it better to subscribe inside a subscription or use rxjs concat with tap是在订阅中订阅还是使用 rxjs concat 更好?
【发布时间】:2021-01-24 14:39:56
【问题描述】:

我在后端保留数据,然后获取该项目的所有保留数据。
获取数据只能在预订之后进行,以便将其包含在内。

我有两个解决方案,但想知道哪个是最好的解决方案,以及在此示例中使用 concat 和 tap 是否有任何优势:

订阅内订阅:

...
this.reserveDataItem();
...

reserveDataItem(){
   this.myService.reserveData(this.selectedData).subscribe(res => {
     if (res.status == 'SUCCESS'){
       ...logSuccessMessagesAndOtherStuff
     }
     this.getReservedDataItemsId();
   });
 }


getReservedDataItemsId(){
   this.myService.getReservedDataItemsForRequestId(this.requestId).subscribe(res => {
     if (res.status == 'SUCCESS'){
       ..doStuffWithDataItems
     }
   });
 }

concat with tap:
我使用了 tap,因为我找不到如何在单个订阅中处理不同的返回类型
因此我想知道这里是否有任何优势..

...
concat(this.reserveDataItem(), this.getReservedDataItemsId())
   .subscribe();
...

reserveDataItem(): Observable<ApiResponseDto<any>>{
  return this.myService.reserveData(this.selectedData).pipe(
    tap(res => {
      if (res.status == 'SUCCESS'){
        ...logSuccessMessagesAndOtherStuff
      }
    })
  )
 }


getReservedDataItemsId():Observable<ApiResponseDto<DataItemDto[]>>{
   return this.myService.getReservedDataItemsForRequestId(this.requestId).pipe(
    tap(res => {
       if (res.status == 'SUCCESS'){
         ..doStuffWithDataItems
       }
     })
  )
 }

【问题讨论】:

    标签: javascript angular typescript rxjs observable


    【解决方案1】:

    据我了解您的问题和问题:

    您正在后端写入一些数据(第一个 Observable),在写入/保存数据后,您将收到响应(完成第一个 Observable)。第一个要求的响应状态不一定是“成功”,无论你得到什么响应,你都想发出另一个请求。只有在第一个 Observable 完成后,您才需要发出另一个请求来获取此数据。

    假设您的 reserveData 方法发出一个 Http 请求, 我会使用switchMap:

    "For instance, when using switchMap each inner subscription is completed when the source emits, allowing only one active inner subscription." (来自https://www.learnrxjs.io/)

    这是我的简化代码,我将如何解决这个问题:

    myObs = new Observable((observer) => {
        observer.next({status: 'SUCCESS'});
        observer.complete();
    });
    
    myObs2 = new Observable((observer) => {
        observer.next({status: 'SUCCESS'});
        observer.complete();
    });
    
    this.myObs.pipe(
      switchMap((resp) => {
        if (resp.status === 'SUCCESS') ...logSuccessMessagesAndOtherStuff;
        return this.myObs2;
      })
    ).subscribe(resp => {
      if (resp.status === 'SUCCESS') ...doStuffWithDataItems;
    })
    

    这是我的问题,与您的问题有点相关。 Http 请求/响应会产生“仅返回最后一个通知(HTTP 请求的结果)”,因此我认为您不需要使用 mergeMap 或 concatMap(再次假设您的 reserveData方法基本上是发出一个 Http 请求并返回一个响应)。

    【讨论】:

    • 确实问题的前提是正确的。两个订阅都简单地进行 http 调用。感谢您的解决方案,但问题是......在这个用例中使用 rxjs 解决方案而不是内部订阅有什么优势吗?
    • 哦,好吧。所以,在我看来,这是一个干净、可读和现代的代码的问题。我真的不知道使用 rxjs switchMap 是否更快,但通过使用它,你肯定表明你知道你在做什么/你想要归档什么,并且代码很容易扫描和理解。跨度>
    • 由于您的问题只是关于顺序可观察对象,我认为最大的不同是样式,但是如果您询问 mergeMap 与 Promises 等不同的方法,在这里您可以阅读一篇关于优点的好文章RxJS。 medium.com/@ravishivt/batch-processing-with-rxjs-6408b0761f39 。我认为,这篇文章的摘要很好地回答了您的问题。
    【解决方案2】:

    使用 forkJoin 代替:

        const result1 = this.myService.reserveData(this.selectedData);
        const result2 = this.myService.getReservedDataItemsForRequestId(this.requestId);
        
        getData(): Observable<any> {
          return forkJoin([result1, result2]);
        }
        
        this.getData().subscribe(res => {
           //...logSuccessMessagesAndOtherStuff res[0]
           //..doStuffWithDataItems res[1]
        }, err => {
           console.log(err);
        });
    

    【讨论】:

    • 好的,但是 forkJoin 并不能确保(据我所知)第一个 observable 将在调用第二个 observable 之前完成。所以这不是一个好的解决方案
    • forkJoin 在所有 observable 完成后从 result 发出最后一个发出的值,那么如果内部 observable 没有完成,forkJoin 将永远不会发出值!
    • 是的...但是如果getReservedDataItemsId先完成,它不会在后端使用reserveDataItem函数保存尚未完成保留的新保留数据。对吗?这意味着将发出的值对于问题中提到的用例将不正确。
    • 啊,好吧,我认为我第一次不理解您的要求,因此您将使用另一个 RxJs 运算符,例如(mergeMap 或 switchMap)
    【解决方案3】:

    这应该在没有嵌套订阅的情况下模仿您的代码:

    this.myService.reserveData(this.selectedData).pipe(
      mergeMap(res => {
        if (res.status == 'SUCCESS'){
          ...logSuccessMessagesAndOtherStuff
        }
        return this.myService.getReservedDataItemsForRequestId(this.requestId)
      })
    ).subscribe(res => {
      if (res.status == 'SUCCESS'){
        ..doStuffWithDataItems
      }
    });
    

    如果不了解更多关于您在这里做什么的信息,很难就哪个更好提供建议。上面的代码允许您通过额外的转换(通过地图)或过滤器来扩展您的数据管道,而不会有太多麻烦。这样您就不会继续嵌套越来越多的订阅。

    例如,您可以过滤掉失败,因为您现在只是忽略它们,那么这再次等效于上面的代码。

    this.myService.reserveData(this.selectedData).pipe(
      mergeMap(res => {
        if (res.status == 'SUCCESS'){
          ...logSuccessMessagesAndOtherStuff
        }
        return this.myService.getReservedDataItemsForRequestId(this.requestId)
      }),
      filter(res => res.status == 'SUCCESS')
    ).subscribe(res => {
      // Now we already *know* that res.status is 'SUCCESS' here
      // No need to check
      ..doStuffWithDataItems
    });
    

    如果我想从后端获取特定格式的数据,或者我只想要一个子集,该怎么办?让我们假设结果返回具有名字、姓氏和当前年龄的用户。我希望名字是常规的(第一个字母大写,其余小写),姓氏全部大写。我还需要计算当年的生日。

    this.myService.reserveData(this.selectedData).pipe(
      mergeMap(res => {
        if (res.status == 'SUCCESS'){
          ...logSuccessMessagesAndOtherStuff
        }
        return this.myService.getReservedDataItemsForRequestId(this.requestId)
      }),
      filter(res => res.status == 'SUCCESS'),
      map(res => ({
        firstName: res.userdata.fname[0].toUpperCase() + res.userdata.fname.toLowerCase().slice(1),
        lastName: res.userdata.lname.toUpperCase(),
        age: (2020 - res.userdata.birthyear)
      }))
    ).subscribe(user => {
      ..doStuffWithUser
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-02-18
      • 2021-10-24
      • 1970-01-01
      • 1970-01-01
      • 2021-12-19
      • 2022-07-31
      • 1970-01-01
      相关资源
      最近更新 更多