rxjs中的嵌套HTTP控制流

时间:2018-07-18 18:58:48

标签: angular rxjs observable

试图绕过Observables并链接/嵌套HTTP请求。

假设我的Dog-Walking API后端具有以下REST端点,这些端点无法更改:

  • GET /dogs(返回所有狗):

    [
        { id: 1, name: 'Fido' },
        { id: 2, name: 'Barky' },
        { id: 3, name: 'Chip' },
        { id: 4, name: 'Bracken' }
    ]
    
  • GET /walker/:id(返回一个dog狗犬):

    { id: 1, name: 'John Doe' }
    
  • GET /pairings(返回狗和步行者之间的所有配对):

    [
        { id: 1, dogIds: [2], walkerId: 1 },
        { id: 2, dogIds: [1, 3], walkerId: 2 }
    ]
    

业务规则

  • 一个配对恰好有1个助行器
  • 配对中有1条或更多的狗的列表
  • 狗可以是0或1对的一部分
  • 助行器可以是0或1个配对的一部分

客观

我想提供一个步行者和狗之间所有配对的列表,按步行者名称排序。我想按名称对每个步行者的狗进行分类。我不想显示没有主动配对的任何步行者或狗,例如:

Walker       | Dogs
-------------+-----------
John Doe     | Barky
Jan Kowalksi | Chip, Fido

我的思考过程

  1. 同时请求所有/pairings/dogs
  2. 等待这两个请求都完成
  3. 遍历每个pairing,然后填充dogs字段
  4. 从每个walkerId中抽出pairing,并并行请求每个/walker/:id
  5. 等待所有这些请求完成
  6. 遍历每个pairing,然后填充walker字段

我觉得我可以使用Promises轻松地做到这一点,但是我正在努力使自己的大脑适应在Observables中的思考。这是到目前为止(使用Angular的HttpClient):

function getDogWalkerPairings() {
    return Observable.forkJoin([
        this.http.get('/pairings'),
        this.http.get('/dogs')
    ])
        .map(
            (res) => {
                const pairings = res[0];
                const dogs = res[1];

                return pairings.map(p => {
                    const pDogs = p.dogIds.map(dogId =>
                        dogs.find(d => (d.id === dogId)
                    );
                    return Object.assign({ dogs: pDogs }, p);
                });
            }
        )
        .map((pairingsWithDogs) => {
            return Observable.forkJoin(
                pairingsWithDogs.map(p => this.http.get('/walkers/' + p.walkerId))
            );
        })
        .map((walkers) => {
            // uhhh... where to now?
            // I don't have a reference to pairings in this scope :/
        });
}

2 个答案:

答案 0 :(得分:1)

好的,我尝试一下:-)

我的方法是尽可能地提取到函数中。对我来说,这有助于获得更好的画面。 我将其从“ .map()”更改为“ pipe(map())”,这是v5.5以来的新RxJs样式。

function getDogWalkerPairings() {
    return Observable.forkJoin([
        this.http.get('/pairings'),
        this.http.get('/dogs')
    ]).pipe(
        map([pairings, dogs] => createPairingsWithDogs(pairings, dogs) ),
        switchMap( pairingsWithDogs => getWalkersForPairs(pairingsWithDogs) )
    )
}

function createPairingsWithDogs(pairing, dogs){
    return pairings.map(pairing => {
        const dogPairings = pairing.dogIds.map(
            dogId => dogs.find( dog => dog.id === dogId)
        );
        return Object.assign( {dogs: dogPairings }, pairing )
    }
}

function getWalkersForPairs(pairingsWithDogs):Observable<any>{
    return Observable.forkJoin(
        pairingsWithDogs.map(p => this.http.get('/walkers/' + p.walkerId))
    ).pipe(
        map( walkerArray => createWalkerDogPairs(walkerArray, pairingsWithDogs) )
    );
}

function createWalkerDogPairs(walkerArray, pairingsWithDogs){
    ...
    return finalResultTable;
}

它如何工作? 首先,我像您一样创建配对。

然后更改流(switchMap)。在这里,我对forkJoin使用技巧。但是,当我将其提取到它自己的函数中时,我会创建一个新的作用域...并且这里有我需要的一切。 (好吧,那里没有cookie,所以不是所有的东西... :-()

如果这是我的编码,我还会添加很多类型。特别是当我切换类型(带有“地图”)时,这可以帮助我保持领先地位

Observable.of( [1,2,3,4] ).pipe(
 map( (numbers: number[]): boolean[] => checkOddNumbers(numbers) ),
 tap( (data: boolean[] => console.log(data) )
)

我希望能有所帮助。

热烈的问候

PS:我知道,我的方法名很棒...:-(

答案 1 :(得分:1)

使用concatMap链接最终的http呼叫。棘手的部分是您需要传回您为第一个forkJoin获得的配对。这是我的答案,以及有效的example on stackblitz。我嘲笑了您的http调用,延迟了500ms,只是在使用它们的地方简单地使用了适当的http调用。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, of, forkJoin, merge, Subject } from 'rxjs';
import { map, delay, concatMap, takeUntil } from 'rxjs/operators';

interface IdNamePair {
  id: number;
  name: string;
}

interface Pairing {
  id: number;
  dogIds: Array<number>;
  walkerId: number;
  dogs?: Array<IdNamePair>;
  walker?: IdNamePair;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {

  pairings: Array<Pairing>;
  private delay = 500;
  private ngUnsubscribe: Subject<any> = new Subject();

  constructor() { }

  ngOnInit() {
    this.getDogWalkerPairings();
  }

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }

  private getDogWalkerPairings() {
    forkJoin(this.getPairings(), this.getDogs())
      .pipe(
        map(this.mapToPairingsWithDogs),
        concatMap((pairingsWithDogs: Array<Pairing>) => {
          return forkJoin(pairingsWithDogs.map(pair => {
            return forkJoin(this.getWalker(pair.walkerId), of(pair));
          }));
        }),
        map(this.mapToPairingsWithDogsAndWalker),
        takeUntil(this.ngUnsubscribe)
      )
      .subscribe((pairings: Array<Pairing>) => {
        console.log(pairings);
        this.pairings = pairings;
      });
  }

  private mapToPairingsWithDogs(data: [Array<Pairing>, Array<IdNamePair>]): Array<Pairing> {
    const pairings = data[0];
    const dogs = data[1];
    return pairings.map(pairing => {
      const pDogs = pairing.dogIds.map(dogId => dogs.find(d => (d.id === dogId)));
      pairing.dogs = pDogs;
      return pairing;
    });
  }

  private mapToPairingsWithDogsAndWalker(data: Array<[IdNamePair, Pairing]>): Array<Pairing> {
    return data.map(d => {
      const pairing: Pairing = d[1];
      pairing.walker = d[0];
      return pairing;
    });
  }

  private getDogs(): Observable<Array<IdNamePair>> {
    return of([
      { id: 1, name: 'Fido' },
      { id: 2, name: 'Barky' },
      { id: 3, name: 'Chip' },
      { id: 4, name: 'Bracken' }
    ]).pipe(delay(this.delay));
  }

  private getWalker(id: number): Observable<IdNamePair> {
    return of({ id: id, name: id === 1 ? 'John Doe' : 'Jane Doe'}).pipe(delay(this.delay));
  }

  private getPairings(): Observable<Array<Pairing>> {
    return of([
      { id: 1, dogIds: [2], walkerId: 1 },
      { id: 2, dogIds: [1, 3], walkerId: 2 }
    ]).pipe(delay(this.delay));
  }

}

编辑

说明:

  1. forkJoin-将同时返回配对
  2. map-将配对配对,我们在其中找到它们的ID
  3. concatMap-将执行下一个调用,为此,我们需要做一些事情
    • 我们需要为每个配对调用getWalker,我们需要同时获取所有结果,因此我们将每个 pairing 映射到一个getWalker方法调用返回一个Observable,最后我们forkJoin映射了Observable数组
    • 棘手的部分是,我们需要将仅在concatMap范围内可用的配对传递给下一个mapsubscribe,我们需要forkJoin中每个Observable的{​​{1}},其中每个getWalker都是根据单个配对创建的Observable
  4. map-将 walker pairing 配对,我们在其中找到他们的ID