我想知道使用RxJS库执行3个依赖于先前结果的http请求的最佳方法是什么。
让我们想象一下,我在Angular应用程序中有3个服务,每个服务都有一个函数get(id:number)用于订阅可观察的请求实体。
我需要调用第一个服务的序列,以通过使用第二个服务获得包含下一个呼叫所需标识符的实体,该实体还包含使用第三个服务的下一个呼叫所需的标识符。
const firstEntityId = 1;
this.firstService.get(firstEntityId)
.subscribe((firstEntity: FirstEntity) => {
this.firstEntity = firstEntity;
this.secondService.get(firstEntity.secondEntityId)
.subscribe((secondEntity: SecondEntity) => {
this.secondEntity = secondEntity;
this.thirdService.get(secondEntity.thirdEntityId)
.subscribe((thirdEntity: ThirdEntity) => {
this.thirdEntity = thirdEntity;
});
});
});
const firstEntityId = 1;
this.getFirstSecondThird(firstEntityId)
.subscribe(([firstEntity, secondEntity, thirdEntity]: [FirstEntity, SecondEntity, ThirdEntity]) => {
this.firstEntity = firstEntity;
this.secondEntity = secondEntity;
this.thirdEntity = thirdEntity;
});
getFirstSecondThird(id: number): Observable<[FirstEntity, SecondEntity, ThirdEntity]> {
return this.firstService.get(id).pipe(
switchMap((firstEntity: FirstEntity) => forkJoin(
of(firstEntity),
this.secondService.get(firstEntity.secondEntityId)
)),
switchMap(([firstEntity, secondEntity]: [FirstEntity, SecondEntity]) => forkJoin(
of(firstEntity),
of(secondEntity),
this.thirdService.get(secondEntity.thirdEntityId)
))
);
}
在这种情况下,使用stream的方法是否最快?
还有另一种方法来编写我的函数getFirstSecondThird而不是使用switchMap和forkJoin方法吗?
(我见过CombineLatest,但是我没有找到如何从先前结果中传递参数)
答案 0 :(得分:1)
也许在方法1中使用map
代替subscribe
?
注意,您需要返回所有嵌套级别。在该示例中,我删除了方括号,因此暗示了返回值。
getFirstSecondThird(id: number): Observable<[FirstEntity, SecondEntity, ThirdEntity]> {
return this.firstService.get(id).pipe(
mergeMap((first: FirstEntity) =>
this.secondService.get(first.secondEntityId).pipe(
mergeMap((second: SecondEntity) =>
this.thirdService.get(second.thirdEntityId).pipe(
map((third: ThirdEntity) => [first, second, third])
)
)
)
)
)
}
这是一个测试代码段,
console.clear()
const { interval, of, fromEvent } = rxjs;
const { expand, take, map, mergeMap, tap, throttleTime } = rxjs.operators;
const firstService = (id) => of(1)
const secondService = (id) => of(2)
const thirdService = (id) => of(3)
const getFirstSecondThird = (id) => {
return firstService(id).pipe(
mergeMap(first =>
secondService(first.secondEntityId).pipe(
mergeMap(second =>
thirdService(second.thirdEntityId).pipe(
map(third => [first, second, third])
)
)
)
)
)
}
getFirstSecondThird(0)
.subscribe(result => console.log('result', result))
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.3/rxjs.umd.js"></script>
如果有可能第二次调用switchMap()
,但是在第一次调用的所有提取完成之前,可以使用mergeMap()
而不是getFirstSecondThird()
, 您要放弃第一个电话-例如在增量搜索方案中。
答案 1 :(得分:0)
我将使用tap
运算符。它通常用于调试目的,但是在需要实现副作用(特别是在可观察对象链中)时非常有用。
this.firstService.get(firstEntityId).pipe(
tap((firstEntity: FirstEntity) => this.firstEntity = firstEntity),
switchMap((firstEntity: FirstEntity) => this.secondService.get(firstEntity.firstEntityId)),
tap((secondEntity: SecondEntity) => this.secondEntity = secondEntity),
switchMap((secondEntity: SecondEntity) => this.thirdService.get(secondEntity.secondEntityId))
).subscribe((thirdEntity: ThirdEntity) => {
this.thirdEntity = thirdEntity;
// Rest of the code goes here
});
您甚至还可以使用tap
来分配this.thirdEntity
,然后仅使用subscribe后续代码。
答案 2 :(得分:0)
如果您使用内部Observable代替,则不需要forkJoin
:
getFirstSecondThird(id: string): Observable<[FirstEntity, SecondEntity, ThirdEntity]> {
return this.firstService.get(id).pipe(
switchMap(first =>
this.secondService
.get(first.secondEntityId)
.pipe(map(second => [first, second]))
),
switchMap(([first, second]: [FirstEntity, SecondEntity]) =>
this.thirdService
.get(second.thirdEntityId)
.pipe(map(third => <[FirstEntity, SecondEntity, ThirdEntity]>[first, second, third]))
)
);
}
这是上下文中带有测试的整个代码:
type FirstEntity = {id: string, secondEntityId: string};
type SecondEntity = {id: string, thirdEntityId: string};
type ThirdEntity = {id: string};
const FIRST_ENTITY: FirstEntity = {id: 'first', secondEntityId: 'second'};
const SECOND_ENTITY: SecondEntity = {id: 'second', thirdEntityId: 'third'};
const THIRD_ENTITY: ThirdEntity = {id: 'third'};
class X {
firstService = {get: (id) => of(FIRST_ENTITY)};
secondService = {get: (id) => of(SECOND_ENTITY)};
thirdService = {get: (id) => of(THIRD_ENTITY)};
getFirstSecondThird(id: string): Observable<[FirstEntity, SecondEntity, ThirdEntity]> {
return this.firstService.get(id).pipe(
switchMap(first =>
this.secondService
.get(first.secondEntityId)
.pipe(map(second => [first, second]))
),
switchMap(([first, second]: [FirstEntity, SecondEntity]) =>
this.thirdService
.get(second.thirdEntityId)
.pipe(map(third => <[FirstEntity, SecondEntity, ThirdEntity]>[first, second, third]))
)
);
}
}
describe('X', () => {
it('getFirstSecondThird', async () => {
// setup
const x = new X();
const firstSpy = spyOn(x.firstService, 'get').and.callThrough();
const secondSpy = spyOn(x.secondService, 'get').and.callThrough();
const thirdSpy = spyOn(x.thirdService, 'get').and.callThrough();
// execution
const result = await x.getFirstSecondThird('first').pipe(toArray()).toPromise();
// evaluation
expect(result[0]).toEqual(<any[]>[FIRST_ENTITY, SECOND_ENTITY, THIRD_ENTITY]);
expect(firstSpy.calls.allArgs()).toEqual([['first']]);
expect(secondSpy.calls.allArgs()).toEqual([['second']]);
expect(thirdSpy.calls.allArgs()).toEqual([['third']]);
});
});