1

I have a typescript array this.products I need to loop over the elements of the array and for each element send parameters to Angular service which makes an API call and gets an answer to the client as an Observable. However, due to asynchronous nature of Observable, my loop finishes before all of the answer are sent back from the server. This is my code:

this.products.forEeach((ele, idx) => {
     this.myService.getSomeDetails(ele.prop1, ele.prop2).subscribe(result => {
         // do something with result 
     });   
});

I need for the loop to advance only after the completion of each observable subscription. How can I implement it? Thanks.

dextercom
  • 162
  • 1
  • 2
  • 13
  • Possible duplicate of [Using async/await with a forEach loop](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – Siyu Dec 20 '18 at 09:51

4 Answers4

4

What you are looking for is forkJoin:

https://rxjs-dev.firebaseapp.com/api/index/function/forkJoin

Map your array of items to an array of api call observables, and pass them into forkJoin. This will emit an array of all your resolved api calls.

Quick and dirty example:

forkJoin(this.products.map(i => this.myService.getSomeDetails(ele.prop1, ele.prop2))).subscribe(arrayOfApiCallResults => {
    // get results from arrayOfApiCallResults
})
Davy
  • 6,295
  • 5
  • 27
  • 38
  • @dextercom Although it works, it has some limitations : `forkJoin` works only with observables that have finished emitting (such as HTTP calls), and it does not allow one to listen to every observable separately. Keep those informations in mind when using it, it might save you hours of debugging and head scratching ! –  Dec 20 '18 at 12:33
  • 1
    @trichetriche Thanks, for my application its just what I need. But I will heed your warning. – dextercom Dec 20 '18 at 12:36
2

You don't need async/await keywords to make your call in sequence.

import { concat } from 'rxjs';

concat(this.products.map(ele => this.myService.getSomeDetails(ele.prop1, ele.prop2)))
  .subscribe(
    response => console.log(response),
    error => console.log(error),
    () => console.log('all calls done')
  )
0

Try this:

let results = await Promise.all(this.products.map(ele => 
    this.myService.getSomeDetails(ele.prop1, ele.prop2).toPromise()
));

// put code to process results here

Requests are sent parallel. Rememeber to add async keyword in function definition which use above code. More info here.

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • 2
    I would add that `await` needs to be used within an `async` function. In that respect you might as well use a normal function and just `return Promise.all(promises)`. It comes down to the same thing. – trincot Dec 20 '18 at 09:53
  • 1
    Going from Observables to Promises is a step back. You don't need to do that if you just want to make sequenced calls. –  Dec 20 '18 at 09:57
  • Combine Promises with Observables is not step back. As you can see when you use promises your code is simpler and less nested (looks like synch code) - this is why I choose this approach. – Kamil Kiełczewski Dec 20 '18 at 10:25
  • It is indeed a step back. Promises can't do what observables do (like cancelling for instance) and they add unecessary complexity (`async/await` is pretty new, not that many people know how to use it, even you made a mistake for the `await` keyword). As for the nested argument, your code is as nested as any observable, you simply omitted the `then` keyword. Other than that, you're free to choose whatever approach you feel like using, But I maintain my statement : if you're using observables and go back to promises, you make a step back. –  Dec 20 '18 at 12:31
  • @trichetriche You say that using new JS keywords is step back? In terms of further results processing my code is flat, other solutions are nested. In my code, below `;` you can process loaded `result` directly - in other solutions there is used `subscribe` which required to put processing code in function (so no directly - and this cause that code is more nested). I dont say that Promieses are better/worse than Observables - but in this case combine botch is better. I don't see my mistake in `await` keyword - can you point where it is exactly? – Kamil Kiełczewski Dec 20 '18 at 12:46
  • My bad on the indent, forgot we were talking about async await and that they don't need `then` keywords. See how confusing it gets ? And you have no mistake in await, but it looks like the first comment is about you forgetting the `await` keyword ? And when I say this is a step back, I'm talking about features, not about timelines. Observables do way more than promises, that's a fact you can't go against. But then again, if you're comfortable with it, keep using that ! But at some point, you'll have to go out of your comfort zone and use Observables you know :) –  Dec 20 '18 at 13:08
0

This is good way to code it.

from(this.products).pipe(mergeMap(
     ele => this.myService.getSomeDetails(ele.prop1, ele.prop2)
)).subscribe(result => {
     () => console.log('.'),
      e => console.error(e),
     () => console.log('Complete')
});