Is this good practice using Observable with async / wait?

I use angular 2 generic http that return an Observable, but I ran into a problem that my code loves the grid when I use the nested Observable call:

this.serviceA.get().subscribe((res1: any) => { this.serviceB.get(res1).subscribe((res2: any) => { this.serviceC.get(res2).subscribe((res3: any) => { }) }) }) 

Now I want to use async / await to avoid this, but async / await only works with Promise. I know that Observable can be converted to Promise, but as I know, this is not a good practice. So what should I do here?

By the way, it would be nice if someone can give me sample code to solve this problem with async / wait: D

+5
source share
2 answers

Observation chain in sequence, as you want to do in your code

As for your sample code, if you want to bind Observables (starting another after the previous emissions), use flatMap (or switchMap ) for this purpose:

 this.serviceA.get() .flatMap((res1: any) => this.serviceB.get()) .flatMap((res2: any) => this.serviceC.get()) .subscribe( (res3: any) => { .... }); 

This practice is better than nesting, as it will make everything clearer and help you avoid callback addons, which Observable and Promises should have helped prevent in the first place.

Also, consider using switchMap instead of flatMap , basically it will allow you to “cancel” other requests if the first of them returns a new value. It's nice to use it if the first Observable that fires the rest is some kind of click event on a button, for example.

If you don’t need your various requests to wait for each other in turn, you can use forkJoin or zip to start them all at once, see @Dan Macak answer for more information and other ideas.


Angular 'async' and Observables work well together

As for Observables and Angular, you can ideally use | async | async pipe in Angular template instead of subscribing to Observable in its component code to get the values ​​emitted by this Observable


ES6 async / await and Promises instead of Observables?

if you don’t feel that you are using Observable directly, you can simply use .toPromise() in your Observable and then some async / wait statements.

If your Observable should return only one result (as is the case with the main API calls), Observable can be considered as completely equivalent to the promise.

However, I'm not sure if this should be done, given all the materials that Observable already provides (to readers: educational counter-examples are welcome!). I would like to use Observables more whenever possible as a training exercise.


Some interesting blog article about this (and there are many others):

https://medium.com/@benlesh/rxjs-observable-interop-with-promises-and-async-await-bebb05306875

The toPromise function is actually a bit complicated, because it is really an “operator”, but rather its RxJS-specific means of subscribing to the Observation and assuring it of a promise. The promise will be resolved to the last emitted value of the Observed after the Observed is completed . This means that if the Observer emits a hello value then waits 10 seconds before it ends, a promise is returned that will wait 10 seconds before allowing a hello. If the observable never ends, then the promise is never resolved.

NOTE: using toPromise () is antipattern unless youre dealing with an API that Promise expects, such as async-wait

(my emphasis)


The example you requested

By the way, it will be nice if someone can give me sample code to solve this with async / await: D

Example, if you really want to do this (maybe with some errors, you can’t check right now, please feel free to fix it)

 // Warning, probable anti-pattern below async myFunction() { const res1 = await this.serviceA.get().toPromise(); const res2 = await this.serviceB.get().toPromise(); const res3 = await this.serviceC.get().toPromise(); // other stuff with results } 

In case you can run all the requests at the same time, await Promise.all() , which should be more efficient, because none of the calls depends on the result of each other. (how forkJoin do with observables)

 async myFunction() { const promise1 = this.serviceA.get().toPromise(); const promise2 = this.serviceB.get().toPromise(); const promise3 = this.serviceC.get().toPromise(); let res = await Promise.all([promise1, promise2, promise3]); // here you can promises results, // res[0], res[1], res[2] respectively. } 
+5
source

Since @ Pac0 was already well versed in various solutions, I just add a slightly different angle.

Mixing Promises and Observables

I personally prefer not to mix Promises and Observables - this is what you get when using async, expecting with Observables, because although they look the same, they are very different.

  • Promises are always async, Observables optional
  • Promises represent only 1 value, observed 0, 1 or many
  • Promises have very limited use, for example, you cannot. cancel them (put aside the following sentences to ES), Observables are much more powerful in using them (you can manage, for example, several WS-connections with them, try with Promises)
  • Their APIs are significantly different.

Using Promises in Angular

Now, although it is sometimes useful to use both parameters, especially with Angular, I think you should consider the possibility of full RxJS . Causes:

  • Most of the Angular API uses Observables (router, http ...), so one of the types comes with a stream (not a pun intended), and not against it, otherwise one would have to convert to Promises all the time, taking advantage of the lost features of RxJS provides
  • Angular has a powerful async channel that allows you to compose the entire data stream of your applications that you filter, combine and perform any modification you want on it, without interrupting the flow of data coming from the server without the only need to create or subscribe . Thus, you do not need to expand the data or assign them to some auxiliary variables, the data just goes from services through Observables directly to the template, which is simply beautiful.

Back to your example

So my recommendation covers both the strength of RxJS and Angula r. Back to your example, you can write the code as follows (credits for @Vayrex idea):

 this.result$ = Observable.forkJoin( this.serviceA.get(), this.serviceB.get(), this.serviceC.get() ); this.result$.subscribe(([resA, resB, resC]) => ...) 

This code fragment will generate 3 requests, and as soon as all these requests are completed, the forkJoin subscription forkJoin will give you the results in an array, and as said, you can subscribe to it manually (as an example) or do it declaratively using result$ and async pipe in the template.

Using Observable.zip , you get the same result, the difference between forkJoin and zip is that the former emits only the last values ​​of the internal Observables, the latter combines the first values ​​of the internal Observables, then the second value, etc.


Edit:. Since you want the results of previous HTTP requests, use the flatMap approach in @ Pac0's answer.

+4
source

Source: https://habr.com/ru/post/1276206/


All Articles