2

I'm struggling a bit with coming up with a good solution for doing multiple http requests, based on the result of each of them. So far, I've been doing good with switchMap, as long as it's only 2 http requests. However, I've found out I need to perform 3, and I'm not sure how to do it with switchMap, or if there's a better solution for it.

Basically, I will await a response, and if it comes back empty, I want to perform a 2nd request, and if that comes back empty, I want to perform a 3rd request.

Let me try to give a simplified example of what I've gotten so far:

getData(): Observable<number> {
  const $subject = new Subject<number>();

  // loadData() takes in an id to specify what data to load
  service.loadData(1).pipe(
    switchMap(response => {
      if(response.length === 0) {
        return service.loadData(2);
      }
      else {
        return of(response);
      }
    }),
  ).subscribe(result => {
    $subject.next(result);
    $subject.complete();
  });
  return $subject.asObservable();

Now this works just fine, my issue is, that calling "loadData(2)", might also give back a response with length = 0, and in that case, I need to call loadData(3).

I tried having another switchMap within the switchMap, but that didn't seem to work properly. So my question is, how would I be able to check the length of the response of loadData(2), and if it's 0, return service.loadData(3) ?

Any help or tips are very welcome :)

Jesper
  • 641
  • 3
  • 11
  • 18
  • take a look at this answer: https://stackoverflow.com/a/53560996/2050306 – robert Mar 13 '20 at 15:18
  • @robert While this answer does look useful, I can't see how it takes the response of each observable into consideration - In my scenario, most cases I will only need to do the first call, while this suggestion seems to always perform all 3 calls – Jesper Mar 14 '20 at 12:41

2 Answers2

2

One straight forward and ugly way would be to subscribe to each call individually and check it's result.

getData(): Observable<number> {
  const $subject = new Subject<number>();

  service.loadData(1).subscribe(
    loadOneResponse => {
      if(loadOneResponse.length === 0) {
        service.loadData(2).subscribe(
          loadTwoResponse => {
            if(loadOneResponse.length === 0) {
              service.loadData(3).subscribe(
                loadThreeResponse => {
                  $subject.next(loadThreeResponse);
                  $subject.complete();
                },
                loadThreeError => {
                }
              );
            } else {
              $subject.next(loadTwoResponse);
              $subject.complete();
            }
          },
          loadTwoError => {
          }
        );
      } else {
        $subject.next(loadOneResponse);
        $subject.complete();
      }
    },
    loadOneError => {
    }
  );

  return $subject.asObservable();
}
ruth
  • 29,535
  • 4
  • 30
  • 57
  • Yeah, I thought about something like this, but I feel like there should be a more elegant approach to this - I might end up doing this though, in case I don't find something better. Thanks for your suggestion though :) – Jesper Mar 13 '20 at 12:56
  • You're welcome. Yeah, there might something more elegant, but sometimes the simple way too doesn't seem so bad. – ruth Mar 13 '20 at 13:03
  • Thank you, I was facing something similar and got a solution working from your idea. https://stackoverflow.com/a/63960012/738690 – sean Sep 18 '20 at 17:08
0

One possible solution is to use from with pipe

and takeWhile -> concatMap -> filter -> tap operators.

getData in service will receive an array of id numbers to try getting data for.

from will emit each id from the ids array.

concatMap does not subscribe to the next observable until the previous completes.

filter will ignore empty results.

takeWhile will cancel the whole stream once take becomes false

  getData(ids: number[]): Observable<number[]> {
    let take = true;
    return from(ids).pipe(
      takeWhile(() => take),
      concatMap(id => this.getDataFromApi(id)),
      filter(result => result.length > 0),
      tap(() => take = false)
    );
  }

Sample usage in the component:

  handleGetData() {
    console.log("component: getData");
    const ids = [1, 2, 3, 4];
    this.dataService.getData(ids).subscribe(data => {
      console.log("got data: ", data);
    });
  }

Working stackblitz

robert
  • 5,742
  • 7
  • 28
  • 37