Angular RxJS最佳实践,根据先前结果订阅多个http请求

时间:2018-12-02 20:05:05

标签: angular rxjs

我想知道使用RxJS库执行3个依赖于先前结果的http请求的最佳方法是什么。

让我们想象一下,我在Angular应用程序中有3个服务,每个服务都有一个函数get(id:number)用于订阅可观察的请求实体。

我需要调用第一个服务的序列,以通过使用第二个服务获得包含下一个呼叫所需标识符的实体,该实体还包含使用第三个服务的下一个呼叫所需的标识符。


方法1:使用三个订阅并将每个结果设置为全局变量

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;

          });
      });
  });

方法2:将函数与流和一个订阅一起使用来设置所有全局变量

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,但是我没有找到如何从先前结果​​中传递参数)

3 个答案:

答案 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']]);
    });
});