避免使用angular,HttpClient和Observables进行回调

时间:2019-05-01 16:02:36

标签: javascript angular typescript promise observable

我目前正在努力将我的头缠在有角度的(2 +),HttpClient和Observables上。

我来自诺言async / await背景,我想在角度上实现的目标是:

//(...) 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;
         });
       })
    });
  }

//...

您可能已经注意到,我要避免这种方式进入“厄运金字塔”。 那么如何避免这种情况写成角度代码段?

谢谢!

Ps:我知道您可以根据.get调用的结果调用.toPromise。 但是,让我们假设我现在想采用整体可观察的方式。

1 个答案:

答案 0 :(得分:3)

使用可观察变量时,您不会经常调用订阅。取而代之的是,您将使用各种运算符将可观察变量组合在一起,形成一系列操作。

要获取一个可观察的输出并将其转换为另一个,基本运算符为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)
)

此代码的问题在于,它创建了一个可观察对象,并吐出了其他可观察对象。如果您愿意,可以进行二维观察。我们需要对此进行扁平化处理,以使我们有一个可观察到的东西,它吐出第二个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运算符突出显示您正在产生副作用的事实。只要可观察对象发出一个值,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
    });
  }