是否有更有效的方式在 Typescript 的订阅中进行订阅

Is There a More Efficient Way to Have a Subscription in a Subscription in Typescript

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

例如我有两个可观察对象和 getSelfPurchases() returns:

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

和第二个,getScriptDetails(32),returns:

{
  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
      }
    });
  });
});

订阅不应嵌套。

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

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

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

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

这是您按照之前的方式 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
);

典型案例swichMap,forkJoin,map

  1. 首先获取列表
  2. 创建一个 observables 数组
  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
            })
        )
   })
)