Избегайте ада обратного вызова с angular, HttpClient и Observables - PullRequest
1 голос
/ 01 мая 2019

Я сейчас изо всех сил пытаюсь обернуть голову вокруг угловых (2+), HttpClient и Observables.

Я пришел из асинхронного / ожидающего фона, и то, что я хотел бы достичь в угловом эквиваленте, эквивалентно:

//(...) Some boilerplate to showcase how to avoid callback hell with promises and async/await
  async function getDataFromRemoteServer() {
    this.result = await httpGet(`/api/point/id`);
    this.dependentKey = someComplexSyncTransformation(this.result);
    this.dependentResult = await httpGet(`/api/point/id/dependent/keys/${this.dependentKey}`);
    this.deeplyNestedResult = await httpGet(`/api/point/id/dependen/keys/${this.dependentResult.someValue}`);
  }

Лучшее, что я мог бы найти в угловых:

import { HttpClient } from `@angular/common/http`;

//(...) boilerplate to set component up.

  constructor(private http: HttpClient) {}

// somewhere in a component.

  getDataFromRemoteServer() {
    this.http.get(`/api/point/id`).subscribe( result => {
       this.result = result;
       this.dependentKey = someComplexSyncTransformation(this.result);
       this.http.get(`/api/point/id/dependent/keys/${this.dependentKey}`).subscribe( dependentResult => {
         this.dependentResult = dependentResult;
         this.http.get(`/api/point/id/dependen/keys/${this.dependentResult.someValue}`).subscribe( deeplyNestedResult => {
            this.deeplyNestedResult = deeplyNestedResult;
         });
       })
    });
  }

//...

Как вы могли заметить, я вступаю в Пирамиду Судьбы с таким подходом, которого я хотел бы избежать. Так, как я мог написать угловой фрагмент таким образом, чтобы избежать этого?

Thx!

PS: Мне известно о том, что вы можете вызвать .toPromise по результату вызова .get. Но давайте просто предположим, что я хочу пойти полным Наблюдаемым путем, пока.

1 Ответ

3 голосов
/ 01 мая 2019

При работе с наблюдаемыми, вы не будете часто звонить по подписке. Вместо этого вы будете использовать различные операторы, чтобы объединить наблюдаемые вместе, образуя конвейер операций.

Чтобы взять выходные данные одной наблюдаемой и превратить ее в другую, основной оператор - map. Это похоже на то, как вы можете .map массив создать другой массив. Для простого примера вот удвоение всех значений наблюдаемой:

const myObservable = of(1, 2, 3).pipe(
  map(val => val * 2)
);
// myObservable is an observable which will emit 2, 4, 6

Сопоставление - это также то, что вы делаете, чтобы взять наблюдаемый для одного http-запроса, а затем сделать еще один http-запрос. Однако нам понадобится еще одна часть, поэтому следующий код не совсем верен:

const myObservable = http.get('someUrl').pipe(
  map(result => http.get('someOtherUrl?id=' + result.id)
)

Проблема с этим кодом в том, что он создает наблюдаемую область, которая выплевывает другие наблюдаемые. 2-мерная наблюдаемая, если хотите. Нам нужно сгладить это так, чтобы у нас была наблюдаемая, которая выплевывает результаты второго http.get. Есть несколько различных способов сделать сглаживание, в зависимости от того, в каком порядке мы хотим, чтобы результаты были в том случае, если несколько наблюдаемых излучают несколько значений. Это не большая проблема в вашем случае, так как каждая из этих наблюдаемых http будет излучать только один элемент. Но для справки, вот варианты:

  • mergeMap позволит всем наблюдаемым работать в любом порядке и выводить в любом порядке поступающие значения. Это имеет свое применение, но также может привести к условиям гонки
  • switchMap переключится на последние наблюдаемые и отменит старые, которые могут быть в процессе. Это может устранить условия гонки и обеспечить наличие только самых последних данных.
  • concatMap завершит всю первую наблюдаемую, прежде чем перейти ко второй. Это также может устранить условия гонки, но не отменит старую работу.

Как я уже сказал, это не имеет большого значения в вашем случае, но я бы порекомендовал использовать switchMap. Так что мой маленький пример выше стал бы:

const myObservable = http.get('someUrl').pipe(
  switchMap(result => http.get('someOtherUrl?id=' + result.id)
)

Теперь вот как я могу использовать эти инструменты с вашим кодом. В этом примере кода я не сохраняю все данные this.result, this.dependentKey и т. Д .:

  getDataFromRemoteServer() {
    return this.http.get(`/api/point/id`).pipe(
      map(result => someComplexSyncTransformation(result)),
      switchMap(dependentKey => this.http.get(`/api/point/id/dependent/keys/${dependentKey}`)),
      switchMap(dependantResult => this.http.get(`/api/point/id/dependent/keys/${dependentResult.someValue}`)
    });
  }

// to be used like:

   getDataFromRemoteServer()
     .subscribe(deeplyNestedResult => {
       // do whatever with deeplyNestedResult
     });

Если для вас важно сохранить эти значения, то я бы рекомендовал использовать оператор касания, чтобы выделить тот факт, что вы генерируете побочные эффекты. tap будет запускать некоторый код всякий раз, когда наблюдаемое выдает значение, но не будет связываться со значением:

  getDataFromRemoteServer() {
    return this.http.get(`/api/point/id`).pipe(
      tap(result => this.result = result),
      map(result => someComplexSyncTransformation(result)),
      tap(dependentKey => this.dependentKey = dependentKey),
      // ... etc
    });
  }
...