1

I need to make all requests in parallel whenever it's possible. Here is the simplified version of my code:

const arr = [
  'https://jsonplaceholder.typicode.com/users/1',
  'https://jsonplaceholder.typicode.com/users/2',
  'https://jsonplaceholder.typicode.com/users/3',
];

let observables = [];
for (let url of arr) {
  observables.push(this.http.get(url));
}

const obs$ = this.http.get('http://localhost:3000/users').pipe(
  map((data: any) => {
    //an array of urls like https://jsonplaceholder.typicode.com/users/5
    let urls = data.data;
    let observables = [];
    for (let url of urls) {
      observables.push(this.http.get(url));
    }
    return observables;
  }),
  mergeMap((data) => forkJoin([...data]))
);

forkJoin([...observables, obs$]).subscribe({
  next: (data) => console.log(data, 'from fork'),
  error:(err)=>console.log(err)
});

So, I have in this case:

  1. An array of observables observables
  2. An observable obs$ that fetches some urls, and upon fetching the urls, I need to make requests to those urls in parallel as well.

I don't care about results, I only care if something errors out. These example code seems to be working properly, however, I've got a couple of questions since I'm trying to wrap my head around RxJS.

  1. Is this a proper way of doing things in order to achieve goals I described above?
  2. mergeMap((data) => forkJoin([...data])) -> mergeMap here basically only helps me getting an Observable out of the nested Observable, right? So I could technically use concatMap or switchMap with the same effect?
Noob
  • 2,247
  • 4
  • 20
  • 31

3 Answers3

2

You actually don't need forkJoin, you can simply create an observable that emits individual urls, then use mergeMap to make the request for that url and emit the results.

Since you have two different url sources, we can define those separately, then just use merge to create a single observable from both sources:

const urls_1$: Observable<string> = from(arr);

const urls_2$: Observable<string> = this.http.get('http://localhost:3000/users').pipe(
  mergeMap(response => response.data)
);

const response$ = merge(urls_1$, urls_2$).pipe(
  mergeMap(url => this.http.get(url))
).

response$.subscribe({
  next: data => console.log(data),
  error: err => console.log(err)
});
  • urls_1$: we use from to create observable that emits the array elements individually

  • urls_2$: we make http call then use mergeMap to emit the received array elements individually. This has the same effect as from above (in fact mergeMap used from internally). You are correct, that you could use any of the higher order operators here.

  • responses$: here we use merge to create a single observable from both of our sources. This means whenever url_1$ or url_2$ emit a value, the "merge observable" will emit it. We then use mergeMap to make the http call an emit its result. Unlike the previous mergeMap, this one cannot be substituted by switchMap or concatMap because you want the requests to be made in parallel. The result here is that our response$ observable will emit the response of each request.

    • if you wanted to limit the number of concurrent requests, you can provide the second concurrency parameter to mergeMap:
mergeMap(url => this.http.get(url), 5) // <-- limit to 5 active requests
BizzyBob
  • 12,309
  • 4
  • 27
  • 51
  • Thank you very much! Your example makes a lot of sense and is much cleaner. Just to clarify: is there anything wrong with the way I implemented things in my example? Or is it more of a weird way of doing things, however, from functional prospective it works the same way? – Noob Jun 28 '23 at 18:14
  • In url2, why do you mergemap on something that is apparently not an observable? – maxime1992 Jun 28 '23 at 19:35
  • @maxime1992 because the data is a `string[]` and I want to emit the strings one at a time. It's basically like doing `mergeMap(arr => from(arr))`. – BizzyBob Jun 28 '23 at 19:54
  • Oh my bad, didn't realize it was an array. – maxime1992 Jun 28 '23 at 20:06
  • @noob - yeah I think the behavior is basically the same if you don't care about the emissions. – BizzyBob Jun 28 '23 at 20:16
1

1. Is this a proper way of doing things in order to achieve the goals I described above?

Yes. It is.

2. mergeMap((data) => forkJoin([...data])) -> mergeMap here basically only helps me getting an Observable out of the nested Observable, right? So I could technically use concatMap or switchMap with the same effect?

Based on what you are trying to achieve using mergeMap is the wisest. Even though concatMap and switchMap have similar effects, they also will add some logic you don't need.

concatMap: Projects each source value to an Observable which is merged in the output Observable, in a serialized fashion waiting for each one to complete before merging the next.

switchMap: Projects each source value to an Observable which is merged in the output Observable, emitting values only from the most recently projected Observable.

  • Thanks for the feedback. When it comes to the second question, my understanding is that because `http.get` only produces one observable and there is no stream of observables, there would be no difference if I use mergeMap, concatMap, or switchMap? Am I misunderstanding something? – Noob Jun 28 '23 at 17:57
  • I was going to answer this question, but I see @BizzyBob already did. – Eliezer Veras Vargas Jun 28 '23 at 18:28
1

Off topic, but please note you could rewrite

const arr = [
  'https://jsonplaceholder.typicode.com/users/1',
  'https://jsonplaceholder.typicode.com/users/2',
  'https://jsonplaceholder.typicode.com/users/3',
];

let observables = [];
for (let url of arr) {
  observables.push(this.http.get(url));
}

to

const observables = [
  'https://jsonplaceholder.typicode.com/users/1',
  'https://jsonplaceholder.typicode.com/users/2',
  'https://jsonplaceholder.typicode.com/users/3',
].map(url => this.http.get(url));

You're also shadowing observables variable by declaring it twice (once at the beginning, and once inside your map). Please use different names (observables1 and observables2 would do the trick).

Random
  • 3,158
  • 1
  • 15
  • 25