【问题标题】:Is There a More Efficient Way to Have a Subscription in a Subscription in Typescript是否有更有效的方式在 Typescript 的订阅中进行订阅
【发布时间】:2020-12-17 15:57:30
【问题描述】:

我在 Typescript 中为 Angular 11 编写了一些代码,我需要从一个可观察对象中获取数据,然后循环通过另一个调用来获取名称.获得名称后,我想将其附加到原始对象。

例如我有两个 observables 并且 getSelfPurchases() 返回:

{
  id: 32
  user_id: 5
  script_id: 78
  pp_id: "76SDI89768UIHUIH8976"
},
{
  id: 33
  user_id: 4
  script_id: 79
  pp_id: "78FGGF78SDF78FSD"
}

第二个,getScriptDetails(32),返回:

{
  sname: "Spell Checker"
  author: 43
  price: 20.99
}

我已经成功地实现了我想做的事情,但我觉得它草率且效率低下。我一直在阅读更多关于 RXJS 的东西,比如 switch map,但我不确定是否可以完成类似的事情。或者也许我选择的方法已经是最好的了。输入?

this.userService.getSelfPurchases().subscribe(response => { // first observable
  this.purchases = response;

  this.purchases.forEach((purchase, index) => { // loop through our results
    this.scriptService.getScriptDetails(purchase.script_id).subscribe(responseTwo => { // second observable
      if (responseTwo[0] && responseTwo[0].sname !== undefined) {
        this.purchases[index].sname = responseTwo[0].sname; // append to our original purchases object
      }
    });
  });
});

【问题讨论】:

  • 是的,你需要 switchMap

标签: angular typescript http rxjs observable


【解决方案1】:

您基本上不想嵌套订阅。与其说是效率问题,不如说是可维护性、可扩展性和(最重要的)可读性。

嵌套订阅很快就会导致回调地狱。以这种方式彻底迷路是非常简单的。虽然我想的一切都有时间和地点。

这是您的代码按照以前的方式 1-1 重写,没有嵌套订阅。我将您的purchases 数组映射到getScriptDetails 调用数组中,然后通过merge 订阅该数组。

this.userService.getSelfPurchases().pipe(
  tap(response => this.purchases = response),
  map(purchases => purchases.map((purchase, index) => 
    this.scriptService.getScriptDetails(purchase.script_id).pipe(
      map(responseTwo => ({index, responseTwo}))
    )
  )),
  mergeMap(scriptDetailsCalls => merge(...scriptDetailsCalls)),
).subscribe(({index, responseTwo}) => {
  if (responseTwo[0] && responseTwo[0].sname !== undefined) {
    // append to our original purchases object
    this.purchases[index].sname = responseTwo[0].sname; 
  }
});

你可以将上面的map和mergeMap组合成一个单独的mergeMap,如下:

this.userService.getSelfPurchases().pipe(
  tap(response => this.purchases = response),
  mergeMap(purchases => merge(...
    purchases.map((purchase, index) => 
      this.scriptService.getScriptDetails(purchase.script_id).pipe(
        map(responseTwo => ({index, responseTwo}))
      )
    ))
  )
).subscribe(({index, responseTwo}) => {
  if (responseTwo[0] && responseTwo[0].sname !== undefined) {
    // append to our original purchases object
    this.purchases[index].sname = responseTwo[0].sname; 
  }
});

旁白:避免使用全局变量

这是对函数“纯度”的个人喜好,但通常更简洁的是避免设置全局变量然后稍后修改它的模式。使测试变得更加困难,因为它使您对该全局变量的状态的保证更少。

this.userService.getSelfPurchases().pipe(
  mergeMap(purchases => forkJoin(
    purchases.map(purchase => 
      this.scriptService.getScriptDetails(purchase.script_id).pipe(
        map(responseTwo => ({...purchase, sname: responseTwo[0].sname}))
      )
    )
  ))
).subscribe(purchasesWName =>
  this.purchases = purchasesWName
);

【讨论】:

  • 更新为我认为更简洁的版本
  • 您的回答非常有见地,尤其是您的添加。感谢您抽出宝贵时间来回答。
  • 问题,但我收到了一个折旧通知,其中 fork join 在您的备用代码上。我猜 forkJoin 现在需要一组 observables。将代码放入括号会导致它无法完成。对此有何建议?
  • purchses.map(...) 返回一个可观察的数组。不知道为什么你会收到弃用通知。
【解决方案2】:

是一个典型的案例 swichMap, forkJoin, map

  1. 先获取列表
  2. 创建一个可观察对象数组
  3. 创建forkJoin
  4. 映射初始列表添加接收到的值

在代码中

this.userService.getSelfPurchases().pipe(
   switchMap(purchases=>{
         //here you has the purchases, e.g. [{script_id:2,script_id:4}]
         //so create an array with the observables
         const obs=purchases.map(purchase=>this.scriptService.getScriptDetails(purchase.script_id))
        //using switchmap we should return an observable
        return forkJoin(obs).pipe(
            //data is an array with the response of the call for script_id:2 and script_id4
            //but we don't want return only an array with the data
            //so we use map to transform the data
            map((data:any[])=>{
               //we loop over purchases to add the properties
               //we get in data
               purchases.forEach((purchase,index)=>{
                  purchase.sname=data[index].sname
                  purchase.author=data[index].author
                  purchase.price=data[index].price
                  purchase.author=data[index].author
               }
               //finally return purchases
               return purchases
            })
        )
   })
)

【讨论】:

    【解决方案3】:

    订阅不应嵌套。

    有可能,例如用于合并订阅的 concat、forkJoin、switchMap 或合并管道。

    嵌套订阅是一种反模式: https://www.thinktecture.com/de/angular/rxjs-antipattern-1-nested-subs/

    RxJs Observables nested subscriptions?

    【讨论】:

    • (我不得不承认我在维护一些嵌套订阅,我设法将一些嵌套订阅在一起,但重构嵌套订阅比从一开始就管道它们更难)
    猜你喜欢
    • 1970-01-01
    • 2019-01-31
    • 2021-08-01
    • 2017-09-10
    • 2016-02-20
    • 1970-01-01
    • 2020-12-29
    • 2019-04-09
    • 2015-08-23
    相关资源
    最近更新 更多