0

I have the following code where I iterate through a list with names and use each name to fetch in this case the respective house. This works fine, but I want to include all promises to a Promise.all() - how can I dynamically add a Promise for each name to a Promise.all()? What's good practice here?

list = [
  'Name1',
  'Name2',
  'Name3'
];

this.houses = new BehaviorSubject<any>([]);

const promises = this.greatHouses.map(name => {
  let house;
  return this.fetchHouse(name).then((result: any[]) => {
    house = result[0];
    return result[0];
  }).then((result: any) => {
    return this.fetchLord(result.currentLord);
  }).then((result: any) => {
    const currentLord = Characters.default.find(char => char.characterName === result.name);
    return [{...house, currentLord}];
  });
});

Promise.all(promises).then(values => {
  console.log(values);
});

fetchHouse(name: string) {
  return this.http.get(`${this.housesUrl}`, {
    params: {
      name
    }
  }).toPromise();
}

fetchLord(url: string) {
  return this.http.get(url).toPromise();
}
Tom
  • 3,672
  • 6
  • 20
  • 52
  • 1
    Just collect the promises you create in an array, then call `Promise.all` on that? The best practice is to use `map` instead of a loop over the `list` array for this, but both are fine. – Bergi Oct 08 '20 at 08:53
  • 1
    Personnaly, I'd use `this.list.map` and `return this.fetchHouse .... etc` then the returned array can be used with Promise.all – Jaromanda X Oct 08 '20 at 08:53
  • 1
    Btw, regarding the `let house`, there are much better [ways to access previous promise results in a .then() chain](https://stackoverflow.com/q/28250680/1048572). – Bergi Oct 08 '20 at 08:55
  • @Bergi Thank you! I'm quite new to Promises - I updated my question with new code - the `Promise.all()` returns an array with the correct length, but each element is undefined. How do I properly use it? – Tom Oct 08 '20 at 09:00
  • @Tom your `map` callback doesn't `return` anything, so `promises` doesn't actually contain the promises. – Bergi Oct 08 '20 at 09:02
  • @Bergi Added return, `Promise.all()` won't log anything in this case even though logging `currentLord` inside of the respective promise works. – Tom Oct 08 '20 at 09:04
  • 1
    Don't forget to add a `.catch(console.error)` in the end. The code in your updated question should work. – Bergi Oct 08 '20 at 09:06
  • @Bergi Thanks! This returns `Http failure during parsing for http://localhost:4200/` - why does ?`Promise.all()` return an error for the http but single promises won't? Added my http calls to the code. – Tom Oct 08 '20 at 09:11
  • 1
    Your single promises do as well, you just never noticed it because you weren't handling errors at all. – Bergi Oct 08 '20 at 09:14

1 Answers1

0

Don't use Promises at all. Learn to work with Observables if you're using Angular and its HttpClient.

greatHouses = [
  'Name1',
  'Name2',
  'Name3'
];

// Construct and array of Observables that fetch your data
const houses$ = this.greatHouses.map(name => 
  this.fetchHouse(name).pipe(
    // map result to house
    map((result: any[]) => result[0]),
    // map house to an Observable that fetches the lord and return the required data
    switchMap((house: any) => this.fetchLord(house.currentLord).pipe(
      // map lord to house with currentLord
      map((lord: any) => {
        const currentLord = Characters.default.find(char => char.characterName === lord.name);
        return { ...house, currentLord };
      })
    ))
  )
);

// Combine an array of completing Observables with forkJoin to one Observable
forkJoin(houses$).subscribe(houses => console.log('houses', houses))

// Return Observables and handle errors with `catchError` directly where they occur.
fetchHouse(name: string) {
  return this.http.get(`${this.housesUrl}`, {
    params: {
      name
    }
  }).pipe(
    catchError(error => {
      console.error('fetchHouse failed', error);
      return of(null);
    })
  );
}

fetchLord(url: string) {
  return this.http.get(url).pipe(
    catchError(error => {
      console.error('fetchLord failed', error);
      return of(null);
    })
  );
}
frido
  • 13,065
  • 5
  • 42
  • 56
  • 3
    This a solution for Observable fans. Way more straightforward with promises. :-) – Roamer-1888 Oct 09 '20 at 17:31
  • @Roamer-1888 Whether you consider something straightforward largely depends on what you know best. So one person might consider the promise-way straightforward while another person considers the observable-way straightforward. The OPs question targets Angular. RxJS is a core dependency of Angular and heavily used throughout Angular. Anyone using Angular should be familiar and comfortable with RxJS. Using `toPromise` on every Observable one encounters isn't good practice in this case. Observables are often more flexible as they can be combine with other Observables and events in multiple ways. – frido Oct 14 '20 at 16:16