How to wait for subscriptions inside a for loop to

2020-03-15 05:50发布

问题:

I have a subscription inside of a for loop that fetches JSON data as a series of "category data" from an external source, then filters that data based on the users current location. What I need is to wait for all of the subscriptions to complete, and then and only then can my app proceed. Right now, it does not wait for all of the subscriptions to complete, it only completes a few, and then proceeds, while the other subscriptions continue in the background.

I've tried the following "brute force" method, where i know a certain number of categories will be added to the filtered array, and it works, but I don't know how to make it work for any situation.

Here's my code:

getMultipleCategoryData(categoryIds: string[]) {
for (let i = 0; i < this.waypointIds.length; i++) {
//after user selects their categories, its added to waypointNames, and occurences() gets the # of occurences of each waypoint name
  let occurences = this.occurences(this.waypointNames[i], this.waypointNames);
  this.categoryApi.getCategoryData(this.waypointIds[i]).toPromise().then(data => {

    let filteredLocs = data.locations.filter(loc => this.distanceTo(loc, this.hbLoc) < MAX_RADIUS);
    let foundLocs = [];
    //fill filteredLocs with first n, n = occurences, entries of data 
    for (let n = 0; n < occurences; n++) {
      if (filteredLocs[n] != undefined) {
        foundLocs[n] = filteredLocs[n];
      }
    }
    //find locations closest to hbLoc (users current location), and add them to waypoints array
    for (let j = 0; j < foundLocs.length; j++) {
      for (let k = 0; k < filteredLocs.length; k++) {
        if (this.distanceTo(this.hbLoc, filteredLocs[k]) < this.distanceTo(this.hbLoc, foundLocs[j]) && foundLocs.indexOf(filteredLocs[k]) < 0) {
          foundLocs[j] = filteredLocs[k];
        }
      }
    }
    if (foundLocs.length > 0 && foundLocs.indexOf(undefined) < 0) {
      for (let m = 0; m < foundLocs.length; m++) {
        this.waypointLocs.push(foundLocs[m]);
      }
    }
  }).then(() => { 
    //this hardcoded, brute force method works, but i'd need it to be more elegant and dynamic
    if (this.waypointLocs.length >= 5) {
      let params = { waypointLocs: this.waypointLocs, hbLoc: this.hbLoc };
      this.navCtrl.push(MapPage, params);
    }
  });
}
}

And the categoryApi.getCategoryData method:

getCategoryData(categoryId): Observable<any> {
    // don't have data yet
    return this.http.get(`${this.baseUrl}/category-data/${categoryId}.json`)
        .map(response => {
            this.categoryData[categoryId] = response.json();
            this.currentCategory = this.categoryData[categoryId];
            return this.currentCategory;
        });
}

Everything is working fine except waiting for the subscriptions to complete, I'd really like a way to determine when all of the subscriptions have completed. Any help is appreciated!

回答1:

You can collect all observables in an array and use forkJoin to wait for all of them to complete:

let observables: Observable[] = [];
for (let i = 0; i < this.waypointIds.length; i++) {
    observables.push(this.categoryApi.getCategoryData(this.waypointIds[i]))
}
Observable.forkJoin(observables)
    .subscribe(dataArray => {
        // All observables in `observables` array have resolved and `dataArray` is an array of result of each observable
    });