等待多个请求完成的正确方法是什么?

问题描述 投票:0回答:3

我有一个组件需要多个 get 请求才能返回我需要的所有数据。这些是彼此独立的,但我想将所有调用放在一起,并在成功完成后加载 UI。

为了实现这一点,我正在使用 forkJoin,但我是 RxJS 的新手,而且我并不完全肯定我做得正确。请看我下面的例子。任何帮助将不胜感激。

import { Component, Input, OnInit, ElementRef, ViewChild } from '@angular/core';
import { Observable, forkJoin, of } from 'rxjs';
import { flatMap } from 'rxjs/operators';
import { DataService, ItemGroupDto} from 'src/app/shared/services/data-service.service';
import { UserService} from 'src/app/shared/services/user-service.service';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.less']
})

export class SampleComponent implements OnInit {

  @Input() itemGroupId: number;
  public datasetOne: Observable<any[]>;
  public datasetTwo: Observable<any[]>;
  public currentApprover: string;

  constructor(
    private _dateService: DateService,
    private _userService: UserService,
  ) {}

  ngOnInit() {
    this.getData();
  }

  private _getApprover(itemGroupId: number) : Observable<ItemGroupDto> {
    const approver = this._userService.getApprover(itemGroupId);
    approver.subscribe(results => {
      const approver = results.approvers.find(approver => (
        approver.Id === 1
      ));
      this.currentApprover = approver.UserFullName;    
    })
    return approver;
  }

  private _sampleDatasetOne() : Observable<any>{
    const sampleDatasetOne = this._dataService.sampleDatasetOne();
    sampleDatasetOne.subscribe(results => {
      this.datasetOne = results;
    })
    return sampleDatasetOne;
  }

  private _sampleDatasetTwo() : Observable<any>{
    const sampleDatasetTwo = this._dataService.sampleDatasetTwo();
    sampleDatasetTwo.subscribe(results => {
      this.datasetTwo = results;
    })
    return sampleDatasetTwo;
  }


  getData() {
    let sampleDatasetOne = this._sampleDatasetTwo();
    let sampleDatasetTwo = this._sampleDatasetTwo();
    let approver = this._getApprover(this.itemGroupId);
    
    forkJoin(sampleDatasetOne, sampleDatasetTwo, approver).subscribe(_ => {
      // all observables have been completed
      this.loaded = true;
    });
  }
}

上面的代码基于我在研究使用 forkJoin 来实现此目的时发现的示例。这非常适合我的要求。它允许我在各自的订阅中操作每个结果,然后当所有可观察值完成时,我可以通过 *ngIf 或等效方法在视图中显示数据。

但是,我也看到过类似下面的例子:

let sampleDatasetOne = this._dataService.sampleDatasetOne();
let sampleDatasetTwo = this._dataService.sampleDatasetTwo();
let approver = this._userService.getApprover(this.itemGroupId);

forkJoin([sampleDatasetOne, sampleDatasetTwo, approver]).subscribe(results => {
      this.datasetOne = results[0];
      this.datasetTwo = results[1];
      const approver = results[2].approvers.find(approver => (
        approver.Id === 1
      ));
      this.currentApprover = approver.UserFullName; 
    });

实际上,我在解释 forkJoin 这种特殊用法的在线示例或教程中看到了很多这种情况。这看起来有点疯狂,难以阅读、维护等。但也许我只是错过了一些关键信息或概念?

本质上,我只是在寻找正确的 Angular/rxJS 方式来等待多个调用完成并处理/操作每个调用的结果。

如果我走在正确的轨道上,有什么我遗漏的、做错的或可以改进的地方吗?

感谢任何人花时间回答并帮助我解决这个问题。

angular typescript rxjs fork-join
3个回答
3
投票

我想说这取决于你在做什么。

如果你只是想让所有 Observables 完成,然后用它们发出的值做一些事情,一些语法糖会有所帮助:

forkJoin([observable1, observable2, observable3])
  // Array destructuring, a cool EcmaScript 6 feature.
  .subscribe(([value1, value2, value3]) => {
     this.value1 = value1;
     this.value2 = value2;
     this.value3 = value3
  }
);

这仍然保留了在将可观察数据传递给 forkJoin 之前对其进行处理的可能性,例如:

const mappedObservable1 = observable1
  .pipe(
     map(value1 => mapToSomething(value1))
     tap(mappedValue1 => this.value1 = mappedValue1)
  );

forkJoin([mappedObservable1, observable2, observable3]).subscribe(() =>
  this.loadingDone = true;
)


1
投票

只要有一些指示,一切似乎都很好:

您最好创建一个服务来抽象出发出 http 请求并返回响应的步骤。本质上是一项充满以下内容的服务:

  getMyEndpointData(): Observable<any> {
        return this.http.get<any>('foo/bar/etc');
  }

  getMyEndpointDataById(id): Observable<any> {
        return this.http.get<any>(`foo/bar/etc/${id}`);
  }

// this is a good way to organize api interactions.

那么对于你的 forkJoin,我更喜欢将可观察的响应分隔成它们自己的变量的语法:

forkJoin([this.myService.getMyEndpointData(), 
          this.myService.getMyEndpointDataById(1)
         ]).subscribe(([data, idData]: any) => {
// do stuff with data recieved
}

如果您想接受失败并仍然完成其他任务,请务必捕获 forkjoin 的内部可观察量中的错误:

forkJoin([this.myService.getMyEndpointData().pipe(catchError(x => of(null))), // this deals with errors and allows other endpoints to succeed, you can do a null check in subscription code.
          this.myService.getMyEndpointDataById(1)
         ]).subscribe(([data, idData]: any) => {
if(data) {
// do stuff with data recieved
}
}

如果你不这样做,并且 forkjoin 中的任何可观察到的内容都会失败,那么整个事情都会失败(如果我没记错的话,会默默地失败)。


0
投票

您提供的代码看起来不错。为了可维护性和可读性,通常最好遵循尾随 $ 来命名可观察量。此外,您无需订阅和设置值,只需维护一个可观察对象,该可观察对象具有以您想要绑定到模板的对象格式映射的值。这将使其代码更少、更干净。

在这种情况下,是这样的:

let sampleDatasetOne$ = this._dataService.sampleDatasetOne();
let sampleDatasetTwo$ = this._dataService.sampleDatasetTwo();
let approver$ = this._userService.getApprover(this.itemGroupId);

let data$ = forkJoin([sampleDatasetOne$, sampleDatasetTwo$, approver$]).pipe(map(results => ({
      dataSetOne: results[0],
      dataSetTwo: results[1],
      approver: results[2].approvers.find(approver => (
        approver.Id === 1
      ))})));

data$ 可以在模板中进行异步管道传输(将订阅和取消订阅)。此外,您的映射函数可以对返回值进行强类型化,以便可以在模板或组件中的其他位置将其作为 Observable 进行检查。希望这对您有帮助。

但是 forkJoin 似乎是在这种情况下使用的正确运算符:)

© www.soinside.com 2019 - 2024. All rights reserved.