是否有更有效的方式在 Typescript 的订阅中进行订阅
Is There a More Efficient Way to Have a Subscription in a Subscription in Typescript
我正在为 Angular 11 在 Typescript 中编写一些代码,我需要从一个 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
- 首先获取列表
- 创建一个 observables 数组
- 创建 forkJoin
- 映射初始列表并添加收到的值
在代码中
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
})
)
})
)
我正在为 Angular 11 在 Typescript 中编写一些代码,我需要从一个 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
- 首先获取列表
- 创建一个 observables 数组
- 创建 forkJoin
- 映射初始列表并添加收到的值
在代码中
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
})
)
})
)