如何以非阻塞方式组合可观察物?

时间:2019-06-12 11:46:46

标签: javascript angular rxjs

我有一组Observable,它们分别检索不同的数据类型。我将那些Observable链接起来,以获得我想要的页面所有数据。 事实是所有这些信息都是独立的,因此加载一个信息不应阻止或干扰加载其他信息。我无法实现。

以下是我目前所做的一个示例:

  getAll(): Observable<any[]> {
    return this.getHome().pipe(
      tap((home: Home) => this.application = home),
      mergeMap((home: Home) =>
        combineLatest([
          this.getAssets(home.images).pipe(defaultIfEmpty([])),
          this.getStyling(home.styling).pipe(defaultIfEmpty({})),
          this.getCategories(home.categories).pipe(defaultIfEmpty([])),
        ])
      )
    );
  }

在这里,Observable已成功链接,所以我得到了我想要的所有数据,但是combineLatest()在前一个请求完成后会执行下一个请求,而在一切完成后,parent将发出收集的数据,这会产生延迟。 我一直在尝试使用merge()而不是combineLatest(),以便接收到的每个数据都立即将其发送给父级Observable,但显然我做不到

那么,我如何管理这些Observable的链接,以便子Observable中的每个提交都直接向父Observable发出?

3 个答案:

答案 0 :(得分:5)

您需要forkJoin,它会返回一个Array和每个Observable的结果(与forkJoin参数的顺序相同。

  getAll(): Observable<any[]> {
    return this.getHome().pipe(
      tap((home: Home) => this.application = home),
      mergeMap((home: Home) =>
        forkJoin(
          this.getAssets(home.images).pipe(defaultIfEmpty([])),
          this.getStyling(home.styling).pipe(defaultIfEmpty({})),
          this.getCategories(home.categories).pipe(defaultIfEmpty([]))
        )
      )
    );
  }

答案 1 :(得分:3)

如果它们是独立的,则可以独立处理它们并将它们发射到主题上。

  getAll(): Observable<any> {
    const subject = new Subject<any>();
    const handler = (res: any) => subject.next(res);

    this.getHome().pipe(
      tap((home: Home) => this.application = home),
      tap(handler),
      tap((home: Home) => {
        forkJoin(
          this.getAssets(home.images).pipe(defaultIfEmpty([]),tap(handler))
          this.getStyling(home.styling).pipe(defaultIfEmpty({}), tap(handler))                  
          this.getCategories(home.categories).pipe(defaultIfEmpty([]), tap(handler))
        ).subscribe(res => subject.complete())
      })
    ).subscribe();
    return subject.asObservable();
  }

使用这种方法,类型检查必须由使用者完成。

Subjects are documented here

答案 2 :(得分:1)

  

我一直在尝试使用merge()而不是CombineLatest(),这样接收到的每个数据都会立即将其发送给父Observable,但是显然我做不到

听起来好像您在准备 partial 数据时就在寻找它。 combineLatest()forkJoin()都只会在可以填充与可观察对象数量相等长度的数组时才发射数据。因此,您要么等待每个可观察值中的至少一个值,要么等待它们全部完成,但是它们发出一个数组,并且他们需要知道要在其中放置什么。

我认为您想在准备好每条数据后立即将其发送出去。

首先使用merge()从所有3个可观察对象中发出值,但是对于每个可观察对象,请使用map()为该给定类型(图像,样式,类别)创建对象键/值对。 / p>

最后,使用scan()运算符将值聚合到一个对象中,该对象将在更新每个属性时发出。

流的消费者可以检查属性是否为null,以查看其是否准备就绪。该流将在完成之前发出3个值。

  getAll(): Observable<{home: Home, images: any[], styling: any[], categories: any[]}> {
    return this.getHome().pipe(
      tap((home: Home) => this.application = home),
      switchMap((home: Home) =>
        merge([
          this.getAssets(home.images).pipe(
             defaultIfEmpty([]),
             map(images => ({images}))
          ),
          this.getStyling(home.styling).pipe(
             defaultIfEmpty({}),
             map(styling => ({styling}))
          ),
          this.getCategories(home.categories).pipe(
             defaultIfEmpty([]),
             map(categories => ({categories})
          ),
        ]),
        scan((acc, next) => ({...acc, ...next}), {home, images: null, styling: null, categories: null})
      )
    );
  }

如果要发出所有属性均为startWith({})的第一个值,可以将merge()运算符添加到null运算符。作为消费者的早期价值,他们知道开始阅读其他价值。