Я пишу некоторый код в 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
}
});
});
});
Подписки не должны быть вложенными.
Есть возможности, например. concat, forkJoin, switchMap или каналы слияния для объединения подписок.
Вложенные подписки — это антипаттерн: https://www.thinktecture.com/de/angular/rxjs-antipattern-1-nested-subs/
Вложенные подписки RxJs Observables?
(Я должен признать, что я поддерживаю некоторые вложенные подписки, мне удалось объединить некоторые из них, но реорганизовать вложенные подписки сложнее, чем связать их с самого начала)
Вы в основном никогда не хотите вкладывать подписки. Дело не столько в эффективности, сколько в удобстве сопровождения, расширяемости и (что наиболее важно) удобочитаемости.
Вложенные подписки быстро приводят к аду обратных вызовов. Удивительно просто так безнадежно заблудиться. Хотя всему свое время и место, я полагаю.
Это ваш код, переписанный 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
);
Обновлено для того, что я считаю более чистой версией
Ваш ответ был очень проницательным, особенно с вашим дополнением. Я ценю, что вы нашли время, чтобы ответить.
Вопрос, однако, я получаю уведомление об амортизации с форк-соединением на вашем стороннем коде. Я предполагаю, что forkJoin теперь принимает массив наблюдаемых. Помещение кода в скобки приводит к тому, что он не завершается. Любое предложение по этому поводу?
purchses.map(...)
возвращает массив наблюдаемых. Не уверен, почему вы получите уведомление об устаревании за это.
это типичный случай swichMap, forkJoin, map
В коде
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
})
)
})
)
Да, вам нужен switchMap