Рекомендации Angular RxJS по подписке нескольких http-запросов в зависимости от предыдущего результата - PullRequest
0 голосов
/ 02 декабря 2018

Я хотел бы знать, как лучше всего использовать библиотеку RxJS для выполнения 3-х http-запросов, что зависит от предыдущего результата.

Давайте представим, что у меня есть 3 сервиса в моем приложении Angular и каждый изу них есть функция 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)
    ))
  );
}

В этом случае метод с использованием потока является самым быстрым?

Есть ли другой способ написать мойфункция getFirstSecondThird вместо использования методов switchMap и forkJoin?

(я видел комбинатно я не нашел, как передать параметр из предыдущего результата)

Ответы [ 3 ]

0 голосов
/ 03 декабря 2018

Может быть, использовать map вместо subscribe в методе 1?

Обратите внимание, вам нужно вернуться на все вложенные уровни.В примере я снял скобки, чтобы подразумевался возврат.

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() во второй раз, но до того, как всезавершены выборки первого вызова, и вы хотите отменить первый вызов - например, в сценарии инкрементального поиска.

0 голосов
/ 10 марта 2019

Вам не нужен forkJoin, если вместо этого вы используете внутреннюю Observable:

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']]);
    });
});
0 голосов
/ 03 декабря 2018

Я бы использовал оператор 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, а такжезатем используйте подписку только для последующего кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...