4

I've got an API call that I need to make multiple times as the API doesn't support requests for multiple items. What I have written so far ends up in an endless loop of sending GET requests to the server. I believe I don't understand the nature of Observables in the below example. My assumption is that each call in the while loop subscribes to a new single object and that when it is resolved it will be placed in the array. How can I modify the below code to achieve what I want?

getSingle(): Observable<Single> {
    return this.http.get(this.url, this.options)
     .map((r: Response) => r.json().data as Single);
}

getMultiple(num: number): Single[] {
    let multiple: Single[] = [];

    let i = 0;
    while (i < num) {
        this.getSingle().subscribe(single => {
            multiple.push(single);
            console.log('Success');
            i++;
        }, err => {
            console.log('Failure');
        });
   }

   return multiple;
}
BreadFish
  • 41
  • 1
  • 2

2 Answers2

4

Using the while loop to make multiple asynchronous HTTP requests, and then subscribe to all of them separately should be avoided in order not to have many Observable connections opened. We can use Observable.forkJoin operator instead.

This is how the implementation should look like:

getSingle(singleUrl: string): Observable<Single> {
    return this.http.get(singleUrl, this.options)
        .map((r: Response) => r.json().data as Single);
};

getMultiple(): Observable<Array<Single>> {
    let singleUrls = ['singleUrl1', 'singleUrl2', 'singleUrl3']; // can be replaced with any 'Single' identifier

    let singleObservables = singleUrls.map((singleUrl: string, urlIndex: number) => {
        return this.getSingle(singleUrl)
            .map(single => single as Single)
            .catch((error: any) => {
                console.error('Error loading Single, singleUrl: ' + singleUrl, 'Error: ', error);
                return Observable.of(null); // In case error occurs, we need to return Observable, so the stream can continue
            });
    });

    return Observable.forkJoin(singleObservables);
};

this.getMultiple().subscribe(
    (singles: Array<Single>) => {
        console.log(singles); // [Single, Single, Single];
        // In case error occured e.g. for the 'singleUrl' (our 'Single' identifier) at position 1,
        // Output wil be: [Single, null, Single];
    }
);

Hope this helps!

seidme
  • 12,543
  • 5
  • 36
  • 40
  • Consider using `mergeDelayError` instead of `forkJoin` with a "hackish" `catch`: It has the benefit that it won't squash the results into an array and when all requests have completed you will get a proper `error` callback in case you had any. – olivarra1 Jan 13 '17 at 08:24
0

Ideally you would only subscribe once in the constructor since a subscribe block can be called many times and then use an RxJS subject and next() it inside the loop

However, a quick fix if you don't care too much is to:

this.getSingle().first().subscribe

you will need to import the first operator. This will ensure subscribe only is called once only. that way you can sub many times no worries.

I'm guessing the reason you are getting infinite HTTP request is that you failure block is being hit and you need to do ...

}, err => {
       i++;
        console.log('Failure');
 });
halfer
  • 19,824
  • 17
  • 99
  • 186
danday74
  • 52,471
  • 49
  • 232
  • 283