Is it good to call subscribe inside subscribe?

JavascriptFunctional ProgrammingRxjsObservable

Javascript Problem Overview


I need to pass three data to one function from three different APIs:

this.service.service1().subscribe( res1 => {
  this.service.service1().subscribe( res2 => {
    this.service.service1().subscribe( res3 => {
      this.funcA(res1, res2, res3);
  });
  });
});

Is it a good practice to subscribe inside a subscribe?

Javascript Solutions


Solution 1 - Javascript

The correct way is to compose the various observables in some manner then subscribe to the overall flow — how you compose them will depend on your exact requirements.

If you can do them all in parallel:

forkJoin(
  this.service.service1(), this.service.service2(), this.service.service3()
).subscribe((res) => {
  this.funcA(res[0], res[1], res[2]);
});

If each depends on the result of the previous, you can use mergeMap (formerly known as flatMap) or switchMap:

this.service.service1().pipe(
  mergeMap((res1) => this.service.service2(res1)),
  mergeMap((res2) => this.service.service3(res2))
).subscribe((res3) => {
  // Do something with res3.
});

... and so on. There are many operators to compose observables to cover lots of different scenarios.

Solution 2 - Javascript

Though all of the above help provide solutions to this particular problem none of them seem to address the obvious underlying problem here, specifically:

> Is it good way to call subscribe inside subscribe?

tl;dr

No it is not good to call a subscribe inside a subscribe.


> Why?

Well because this is not how functional programming is supposed to work. You're not thinking functionally you're thinking procedurally. This isn't necessarily a problem per se, but the whole point of using rxjs (and other reactive programming extensions) is to write functional code.

I'm not going into all the details on what functional programming is but essentially the point of functional programming is to treat data as streams. Streams that are manipulated by functions, and consumed by subscribers. As soon as you add a subscribe inside another subscribe your manipulating data inside a consumer (not inside a stream). So your functional stream is now broken. This prevents other consumers from utilising that stream further down stream in your code. So you've turned your functional stream into a procedure.

enter image description here

Image source, above and more information on pure functional programming here.

Solution 3 - Javascript

You can use forkJoin to combine the Observables into a single value Observable

forkJoin(
  this.service.service1(),
  this.service.service2(),
  this.service.service3()
).pipe(
  map(([res1, res2, res3 ]) => {
    this.funcA(res1, res2, res3);
  })

Solution 4 - Javascript

If the calls can be resolved in parallel you could use forkJoin, like this:

joinedServiceCalls() {
   return forkJoin(this.service1(), this.service2(), this.service3());
}

And then subscribe to that method. https://www.learnrxjs.io/operators/combination/forkjoin.html

Solution 5 - Javascript

Looks strange, I would go this way because it looks cleaner:

async myFunction () {
//...
const res1 = await this.service.service1().toPromise();
const res2 = await this.service.service2().toPromise();
const res3 = await this.service.service3().toPromise();
this.funcA(res1, res2, res3);
//...

}

EDIT

or to do it in parallel

async myFunction () {

//...
let res1;
let res2;
let res3;
[res1,res2,res3] = await Promise.all([this.service.service1().toPromise(),
                                      this.service.service2().toPromise(),
                                      this.service.service3().toPromise()]);
this.funcA(res1, res2, res3);
//...

}

Solution 6 - Javascript

You can use the zip RxJs operator, and then in this case you will only use just one subscribe.

You can then call your function inside that subscribe because all the results are available.

Observable.zip(
  this.service.service1(),
  this.service.service1(),
  this.service.service1()
).subscribe([res1, res2, res3]) {
  this.funcA(res1, res2, res3);
}

Solution 7 - Javascript

as mentioned, forkjoin is a good solution, but it emit completed calls only. If these are values that are going to be emitted repeatedly, use I would combineLatest.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionYashwanth GurrapuView Question on Stackoverflow
Solution 1 - JavascriptMark HughesView Answer on Stackoverflow
Solution 2 - JavascriptLiamView Answer on Stackoverflow
Solution 3 - JavascriptAntoine VView Answer on Stackoverflow
Solution 4 - JavascriptDiego PedroView Answer on Stackoverflow
Solution 5 - JavascripthjbelloView Answer on Stackoverflow
Solution 6 - JavascriptHDJEMAIView Answer on Stackoverflow
Solution 7 - Javascriptuser856510View Answer on Stackoverflow