我只是想学习Angular和RxJS (Observables),我有一些问题链接请求正确的方式。
场景如下:
- 我运行请求A并获得包含数组的JSON对象对象。
- 对于这些对象中的每一个,我需要执行请求B返回一个JSON对象和一个对象数组。
- 对于请求B响应中的每个对象,我需要执行请求C返回一个简单的JSON对象。
- 现在我必须存储从请求C收到的所有对象,并使用信息扩展它们(信息来自对请求D, E和F的回答)。
- 只有当所有的请求都完成时,我想显示我的应用程序的UI,在此之前,只有一个加载旋转器应该是可见的。
我当前的解决方案为我提供了所有必要的信息,但我找不到一种方法来等待所有请求完成。它看起来像这样:
private fetchData(): void {
this.backendServie.getObjectA().pipe(
flatMap((objects: IObjectA[]) => {
// return objects the get them one by one
return objects
}),
flatMap((object: IObjectA) => {
return this.backendService.getObjectB(object.id)
}),
flatMap((objects: IObjectB[]) => {
// return objects the get them one by one
return objects
}),
flatMap((object: IObjectB) => {
return this.backendService.getObjectC(object.id)
}),
flatMap((object: IObjectC) => {
// convert object to make additional fields available
const extendedObject: IObjectCExtended = object as IObjectCExtended;
this.backendService.getAdditionalInformationD(object.id).subscribe((info: string) >= {
extendedObject.additionalInfoD = info;
});
this.backendService.getAdditionalInformationE(object.id).subscribe((info: string) >= {
extendedObject.additionalInfoE = info;
});
this.backendService.getAdditionalInformationF(object.id).subscribe((info: string) >= {
extendedObject.additionalInfoF = info;
})
return extendedObject;
})
).subscribe((extendedObject: IObejectCExtended) => {
this.objects.push(extendedObject);
})
}
不幸的是,这种链接请求的方式感觉是错误的,正如我所说的,我找不到一种方法来检查是否所有的请求都完成了,UI只是一点点地填满了。
我希望我的问题是可以理解的,提前感谢你的帮助。
问好这个设计非常低效,正如前面提到的。所以最好改变你的后端——正如已经在评论中提到的。
如果你不能改变你的后端,你可以使用switchMap
和forkJoin
的组合来分组你的请求。
一次精简所有条目
您可以一次简化想要调用的每个API的所有条目。所以基本上:
- 获取所有对象
- 获取每个对象的所有objectb
- 获取每个对象的所有对象tb
- 获取每个对象的所有添加
- 每增加一个对象扩展所有对象
private fetchData(): void {
// get your initial object
this.backendServie.getObjectA().pipe(
// switch to forkJoin, which waits until all inner observables complete and returns them as an array
switchMap((objects: IObjectA[]) => forkJoin(
// map all your objects to an array of observables which will then be waited on by forkJoin
objects.map(obj => this.backendService.getObjectB(obj.id))
)),
// Again, switch to another forkJoin
switchMap((objects: IObjectB[]) => forkJoin(
// Again, map all your objects to observables that forkJoin will collect and wait on
objects.map(obj => this.backendService.getObjectC(obj.id))
)),
// switch to another forkJoin that retrieves all your extensions for every object
switchMap((objects: IObjectC[]) => forkJoin(
// map each object to a forkJoin that retrieves the extensions for that object and map it to the extended object
objects.map(obj => forkJoin([
this.backendService.getAdditionalInformationD(obj.id),
this.backendService.getAdditionalInformationE(obj.id),
this.backendService.getAdditionalInformationF(obj.id)
]).pipe(
map(([infoD, infoE, infoF]) => ({
...obj,
additionalInfoD: infoD,
additionalInfoE: infoE,
additionalInfoF: infoF
}))
)
/* Alternatively, you can use the dictionary syntax to shorten this
objects.map(obj => forkJoin({
additionalInfoD: this.backendService.getAdditionalInformationD(obj.id),
additionalInfoE: this.backendService.getAdditionalInformationE(obj.id),
additionalInfoF: this.backendService.getAdditionalInformationF(obj.id)
}).pipe(map(additionalInfo => ({...obj, ...additionalInfo })))
*/
))
// You're now getting a list of the extended objects
).subscribe((extendedObjects: IObejectCExtended[]) => {
this.objects = extendedObjects;
});
}
为了使代码更清晰,您可以将不同的部分分组到不同的函数中。可以是这样的
private fetchData(): void {
this.fetchExtendedObjects()
.subscribe(extendedObjects => this.objects = extendedObjects);
}
private fetchExtendedObjects():Observable<IObjectCExtended[]> {
return this.backendServie.getObjectA().pipe(
switchMap(objectsA => this.getAllObjectsB(objectsA)),
switchMap(objectsB => this.getAllObjectsC(objectsB)),
switchMap(objectsC => this.extendAllObjectsC(objectsC))
)
}
private getAllObjectsB(objects: IObjectA[]):Observable<IObjectB[]> {
return forkJoin(objects.map(obj => this.backendService.getObjectB(obj.id)));
}
private getAllObjectsC(objects: IObjectB[]):Observable<IObjectC[]> {
return forkJoin(objects.map(obj => this.backendService.getObjectC(obj.id)));
}
private extendAllObjectsC(objects: IObjectC[]):Observable<IObjectCExtended[]> {
return forkJoin(objects.map(obj => this.extendObjectC(obj)));
}
private extendObjectC(object: IObjectC):Observable<IObejectCExtended> {
objects.map(obj => forkJoin({
additionalInfoD: this.backendService.getAdditionalInformationD(obj.id),
additionalInfoE: this.backendService.getAdditionalInformationE(obj.id),
additionalInfoF: this.backendService.getAdditionalInformationF(obj.id)
}).pipe(map(additionalInfo => ({...obj, ...additionalInfo })))
}
单独精简每个条目
作为对上面的优化,您可以分别简化每个对象,这将给您带来一个小小的性能提升。总的来说,这应该不会有太大的影响,因为您仍然需要等待所有请求完成,但如果您的一些API对于某些对象较慢,这可能会有所帮助。
这基本上意味着:
- 获取所有对象
- 对于每个对象a,获取并行对象b
- 为每个对象获取并行的objectC
- 为每个对象tc获取所有扩展(为每个对象tc并行)
- 为每个objectC扩展objectC与所有扩展(为每个对象tc并行)
- 将所有扩展对象组合成一个数组
private fetchData(): void {
// get your initial object
this.backendServie.getObjectA().pipe(
switchMap((objects: IObjectA[]) => forkJoin(
// Switch each objectA to an observable that retrieves objectB
// In contrast to the first version, this is done for each objectA separately
objects.map(obj => this.backendService.getObjectB(obj.id).pipe(
// Switch each objectB to an observable that retrieves objectC
switchMap((object: IObjectB) => this.backendService.getObjectC(obj.id)),
// Switch each objectC to an observable that retrieves all of the
// extensions an combines them with objectC
switchMap((object: IObjectC) =>
// Retrieves all extensions and provides them as a dictionary
forkJoin({
additionalInfoD: this.backendService.getAdditionalInformationD(obj.id),
additionalInfoE: this.backendService.getAdditionalInformationE(obj.id),
additionalInfoF: this.backendService.getAdditionalInformationF(obj.id)
}).pipe(
// combine the original objectC with all the extensions
map(additionalInfo => ({...object, ...additionalInfo}))
)
)
))
))
// You're now getting a list of the extended objects
).subscribe((extendedObjects: IObejectCExtended[]) => {
this.objects = extendedObjects;
});
}
同样,为了使其更具可读性,您可以将这些部分分成函数:
private fetchData(): void {
this.fetchExtendedObjects().subscribe(objects => this.objects = objects);
}
private fetchExtendedObjects():Observable<IObjectCExtended[]> {
return this.backendServie.getObjectA().pipe(
switchMap((objects: IObjectA[]) => this.getExtendedObjectsC(objects))
);
}
private getExtendedObjectsC(objects: IObjectA[]):Observable<IObjectCExtended[]> {
return forkJoin(objects.map(obj => this.getExtendedObjectC(obj)));
}
private getExtendedObjectC(objectA: IObjectA):Observable<IObjectCExtended> {
return this.backendService.getObjectB(objectA.id).pipe(
switchMap((object: IObjectB) => this.backendService.getObjectC(obj.id)),
switchMap((object: IObjectC) => this.extendObjectC(object))
);
}
private extendObjectC(object: IObjectC):Observable<IObejectCExtended> {
objects.map(obj => forkJoin({
additionalInfoD: this.backendService.getAdditionalInformationD(obj.id),
additionalInfoE: this.backendService.getAdditionalInformationE(obj.id),
additionalInfoF: this.backendService.getAdditionalInformationF(obj.id)
}).pipe(map(additionalInfo => ({...obj, ...additionalInfo })))
}