Есть ли более эффективный способ подписки на подписку в машинописном тексте

Я пишу некоторый код в Typescript для Angular 11, где мне нужно получить данные из одного наблюдаемого, а затем выполнить цикл, чтобы получить еще один вызов, чтобы получить имя. Как только я получу имя, я хочу добавить его к исходному объекту.

Например, у меня есть две наблюдаемые, и 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, как карта переключения, но я не уверен, можно ли что-то подобное сделать. Или, может быть, метод, который я выбрал, уже является лучшим. Вход?

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

Roberto Zvjerković 17.12.2020 17:01
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
1
1 243
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Подписки не должны быть вложенными.

Есть возможности, например. concat, forkJoin, switchMap или каналы слияния для объединения подписок.

Вложенные подписки — это антипаттерн: https://www.thinktecture.com/de/angular/rxjs-antipattern-1-nested-subs/

Вложенные подписки RxJs Observables?

(Я должен признать, что я поддерживаю некоторые вложенные подписки, мне удалось объединить некоторые из них, но реорганизовать вложенные подписки сложнее, чем связать их с самого начала)

Guntram 17.12.2020 17:05
Ответ принят как подходящий

Вы в основном никогда не хотите вкладывать подписки. Дело не столько в эффективности, сколько в удобстве сопровождения, расширяемости и (что наиболее важно) удобочитаемости.

Вложенные подписки быстро приводят к аду обратных вызовов. Удивительно просто так безнадежно заблудиться. Хотя всему свое время и место, я полагаю.

Это ваш код, переписанный 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; 
  }
});

Вы можете объединить карту и карту слияния выше в одну карту слияния следующим образом:

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

Обновлено для того, что я считаю более чистой версией

Mrk Sef 17.12.2020 18:49

Ваш ответ был очень проницательным, особенно с вашим дополнением. Я ценю, что вы нашли время, чтобы ответить.

awild 17.12.2020 18:51

Вопрос, однако, я получаю уведомление об амортизации с форк-соединением на вашем стороннем коде. Я предполагаю, что forkJoin теперь принимает массив наблюдаемых. Помещение кода в скобки приводит к тому, что он не завершается. Любое предложение по этому поводу?

awild 17.12.2020 19:30
purchses.map(...) возвращает массив наблюдаемых. Не уверен, почему вы получите уведомление об устаревании за это.
Mrk Sef 17.12.2020 19:34

это типичный случай swichMap, forkJoin, map

  1. Сначала получите список
  2. Создайте массив наблюдаемых
  3. сделать форкПрисоединиться
  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
            })
        )
   })
)

Другие вопросы по теме