How do I make http.get requests sequential?

Hello!

I’ve been trying to find out if I can make HTTP.get requests one after the other.

I’ve been looking around and I found different options called .success, .then, etc. But I find they are only applicable to ionic1, or AngularJS, or others, apparently.

I tried every single option I found, to no avail. I use Sublime Text and a debugger option that tells me that HTTP.get provides an Observable.

Is there any way to make something like the following with HTTP.get?:

this.http.get('RequestA').success{
   this.http.get('RequestB').success{
      // use RequestA and RequestB response data when both are fully loaded
   }
}

Thanks a million in advance! :slight_smile:

Sidenote: I use the map module to create a json-structured Observable that is easier to handle, hopefully I can use that too?

I’d use good ol’ .reduce in this case, for example:

[
  'first get url',
  'second get url'
].reduce((promise, url) => promise.then(results => this.http.get(url).then(data => [results, ...data])), Promise.resolve([]))
.then(data => {
  let responseA = data[0];
  let responseB = data[1];
});

This looks like a good solution Sigmund, but then again, what could I do if the URL ‘B’ needs data from the data retrieved from ResponseA? I would have to wait for ResponseA to get done first. Is it possible to do what you did but like:

['first get url'].reduce((promise, url) => promise.then(results => this.http.get(url).then(dataA => [results, ...dataA])), Promise.resolve([]))
.then(dataA => {
  let responseA = dataA[0];

  ['second get url' + dataA[0]].reduce((promise, url) => promise.then(results => this.http.get(url).then(dataB => [results, ...dataB])), Promise.resolve([]))
.then(dataB => {
     // got dataA and dataB!
  });
});

This is a code I completely made up without much notion of how everything works regarding promises xD
Still I hope the message is clear!

Oh, apologies. I misunderstood the original post. So if you just have those two requests/responses you’d do something like:

      this.http.get('request a').then(responseA => {
        return this.http.get('request b')
        .then(responseB => [responseA, responseB]);
      })
      .then(data => {
        let responseA = data[0];
        let responseB = data[1];
      });

Or if you prefer:

      let responseA;
      this.http.get('request a').then(r => {
        responseA = r;
        return this.http.get('response b');
      })
      .then(responseB => {
        console.log(responseA);
        console.log(responseB);
      });

The benefit of the former is you don’t nest .then, but you also don’t need to introduce a variable. Not sure which one I prefer myself.

Hey just tried that, but I keep getting the “.then is not a function”…

My specs are the following:
Ionic Framework: 3.9.2
Ionic App Scripts: 3.1.8
Angular Core: 5.0.3
Angular Compiler CLI: 5.0.3
Node: 8.9.4
OS Platform: OS X Yosemite
Navigator Platform: MacIntel
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 OPR/51.0.2830.34

I have no idea why .then is not recognized :frowning:

:thinking: What is HTTP in this case? I was assuming that it was Angular’s HTTP provider.

HTTP is Angular’s HTTP provider, yes.

I import it in the TS’s header:

import { Http } from '@angular/http';

Oh doy. HTTP returns an Observable. My bad.

Something like this should work:

      this.http.get('request a')
      .pipe(flatMap(r => this.http.get('request b').pipe(map(r2 => [r, r2]))))
      .subscribe(data => {
        let responseA = data[0];
        let responseB = data[1];
      });

You’ll also need to add imports for flatMap and map:
import { map, flatMap } from 'rxjs/operators';

I’m afraid I didn’t explain myself properly.

What the code is meant to do is retrieve playlist and video data from the Youtube API.

So RequestA is the URL that asks for the playlists of a channel, then ResponseA will have the playlists’ IDs.
After I get ResponseA, I would go over each Playlist ID to retrieve their videos, so RequestB would be a URL that contains ResponseA[].id to fetch their specific videos.

I think the way you formatted your requests, Sigmund, might not let me do this kind of operation, as I see you put every response at once. The point I want to get to is:
http.get (‘requestA’) -> ResponseA -> http.get(‘requestB’+ResponseA.data) -> ResponseB

Sorry for the confusion! I can’t thank you enough for your help though :sweat:

flatmap is acting as a sort of .then in this case, so it would execute them sequentially. Naturally this will be a little different if Response A is an array…

Do you want to wind up with an array containing all of the videos, or an array grouped by playlists with their videos?

Yeah, both Responses are arrays…

In this case, I need an array with all the playlists and their videos, as I need playlist info AND video info.

As much as I respect @SigmundFroyd’s many contributions here, a couple of rules I try to follow in situations like these:

  • don’t subscribe to observables in the same place you create them
  • don’t nest subscriptions, maps or pipes

Following those leads us to this implementation, which should be pretty easily adapted to your real-world problem:

export class WombatService {
  // equivalent to list of playlists
  numbers(): Observable<number[]> {
    return of([2, 4, 6, 8])
      .pipe(delay(1200));
  }

  // transforms a single playlist ID to its associated video
  square(n: number): Observable<number> {
    return of(n*n)
      .pipe(delay(800));
  }

  // postprocesses the secondary response to mogrify it into whatever format you desire
  wombatify(n: number): string {
    return n + ' wombats';
  }

  // now weave it all together
  wombats(): Observable<string[]> {
    return this.numbers().pipe(
      mergeMap(ns => forkJoin(ns.map(n => this.square(n)))),
      map(sqs => sqs.map(sq => this.wombatify(sq)))
    );
  }
}

export class HomePage {
  wombats: string[] = [];

  constructor(wombatsvc: WombatService) {
    wombatsvc.wombats().subscribe(wombats => this.wombats = wombats);
  }
}

<ion-list>
  <ion-item *ngFor="let wombat of wombats">
    <ion-label>{{wombat}}</ion-label>
  </ion-item>
</ion-list>
1 Like

So essentially, you are giving each request a delay so that they don’t overlap? I could try that, but based on the code you made it’s gonna take me a while to interpret it to my code.

No, you can completely ignore the delays: they’re only there to simulate the latency of making network requests and show that everything is actually going to show up in the template once all the backend stuff has completed.

The important stuff to focus on:

  • a single pipe suffices to chain many (potentially heterogeneous) transformations
  • mergeMap delivers a secondary Observable based on what it is fed
  • forkJoin turns Observable<Foo>[] to Observable<Foo[]>
  • the service should provide the data in a format that is optimally easy for the page/template to display

Obi Wan Kenobi, r u back???

(Sorry, being off topic here)

1 Like

As for my functional contribution:

I learned a lot making a rss reader and http poller using observables

Such a powerfull mindset if u even understand a bit: everything is a stream (courtesy rob wormalds, google it)

Basically, u take an observable stream to start with and enrich it along the way, ending with that one and only perfect output to subscribe to, in a different component

Just the wording of the operators freak me out once in a while

Switchmap, mergemap, concat, forkjoin…

But it is worth the effort to learn and use

Well, lots to process here. Thank you all!

I think I’m going to have to approach this issue some other way, as I only get the data once.

If I manage to get somewhere, I will certainly post the result here.

In the meantime, thanks a lot for your assistance guys!
I’ll leave you with a gif describing my current development situation:

Cheers! :smiley:

try this

Observable.from([Observable.fromPromise(this.http.get()), Observable.fromPromise(this.http.get())])
.subscribe((res) => console.log(res) );

hi
create array of promises to the function u want to download in series then:

let promisesArray=[];
downloadinSeries(promisesArray).then(() => {

        }).catch((error) => {
            console.log(error);
        });

downloadinSeries(arr) {
return arr.reduce(function (promise, item) {
return promise.then(function (result) {
return item();
});
}, Promise.resolve([]));
}