如何并行api调用并将响应的顺序保存在ui列表中(RxJS Observables)

时间:2017-09-13 12:50:18

标签: javascript angular typescript rxjs observable

挑战!

我的问题如下:

我有一个获取Observable的函数,需要丰富人员数据并使用Observable更新观察者

哪个Person对象如下:

export interface Person {
  personId: string;
  children: Child[];
}

export interface Child {
  childId: string;
}

和EnrichPerson看起来像:

export interface EnrichedPerson {
  personName: string;
  parsonCountry: string;
  children: EnrichedChild[]
}

export interface EnrichedChild {
  childName: string;
  childAge: number
}

所以,我做的是这个:

private myFunc(listOfPeople: Observable<Person[]>): void {

  // initializing listOfEnrichedPeople , this will be the final object that will be updated to the behaviour subject 
  // "public currentListOfPeople = new BehaviorSubject<EnrichedPerson[]>([]);"

  let listOfEnrichedPeople: EnrichedPerson[] = [];

  listOfPeople.subscribe((people: Person[]) => {
      people.map((person: Person, personIdx: number) => {
          // here im setting up a new list of enriched children list cause each person have a list like this
          // and for each of the children I need to perform also an api call to get its info - youll see soon
          let listOfEnrichedChildren: EnrichedChild[] = [];
          // here im taking a list of the ids of the people, cause im gonna perform an api call that will give me their names
          let ids: string[] = people.map((person: Person) => person.personId);

          this._peopleDBApi.getPeopleNames(ids).subscribe((names: string[]) => { 
            // here I though if I already have the name I can set it up
              listOfEnrichedPeople.push({
              personName: names[personIdx],
              parsonCountry: "",
              childrenNames: [] });

              // now for each person, i want to take its list of children and enrich their data
              person.childrenIds.map((child: Child) => {
                // the catch is here, the getChildInfo api only perform it per id and cant recieve a list, and I need to keep the order...so did this in the
                  this._childrenDBApi.getChildInfo(child.childId).subscribe((childInfo: ChildInfo) => {
                                listOfEnrichedChildren.push({
                                childName: childInfo.name,
                                childAge: childInfo.age});
                    });
                });
              listOfEnrichedPeople[personIdx].parsonCountry = person.country;
              listOfEnrichedPeople[personIdx].children = listOfEnrichedChildren;
            });
        });
      this.currentListOfPeople.next(listOfEnrichedPeople);
      },
      error => {
        console.log(error);
        self.listOfEnrichedPeople.next([]);
      });
}

我的问题是当我给孩子打电话时,因为如果第一个id需要2秒才能响应而后面的那个只需要1秒就可以丢失我的订单...我需要保留我原来的顺序得到了函数参数...如何让它并行以获得更好的性能并保持我的命令?

2 个答案:

答案 0 :(得分:0)

使用.map()回调的index参数,并通过该索引分配给列表,而不是使用.push()。这样,无论时间如何,api响应都将被分配到列表中的正确位置。

person.childrenIds.map(({child: Child}, index) => {
  this._childrenDBApi.getChildInfo(child.childId).subscribe((childInfo: ChildInfo) => {
    listOfEnrichedChildren[index] = {
      childName: childInfo.name,
      childAge: childInfo.age};
    };
    // ...

答案 1 :(得分:0)

您可以生成一个新的Observable,其中包含从API中获取每个子/人的结果(并行)数组中的原始索引。

然后,您可以将所有这些结果展平为一个新数组,按原始索引对它们进行排序并返回它们

const getEnrichedChildren = (children: Person[]): Observable<EnrichedPerson[]> => 
  //create an observable from the array of children
  Observable.of(...children)
    //map to a new observable containing both the result from the API and the 
    //original index.  use flatMap to merge the API responses
    .flatMap((child, index) => peopleApi.getPerson(child.personId).map(enriched => ({ child: enriched, index })))
    //combine all results from that observable into a single observable containing 
    //an array of EnrichedPerson AND original index
    .toArray()
    //map the result to a sorted list of EnrichedPerson
    .map(unorderedChildren => unorderedChildren.sort(c => c.index).map(c => c.child));

这里的可读性非常糟糕,但我已将所有内容保存在一个区块中,以便您可以看到事物如何链接在一起