0

I need to iterate through a list and make multiple calls that are dependent on the previous call to finish before iterating the loop. Each function call returns a promise.

My current code just iterates through the list and makes the calls a fast as it can like so:

this.records.forEach(item => {
  this.myService.doFirstTask().then(res => {
    if (res) {
      this.myService.doSecondTask().then(res => {
        if (res) {
          // do something.
        }
      });
    }
  });
});

The problem is that before allowing the .forEach to go to the next item, i need to first wait for myService.doSecondTask() to complete and return its promise.

I know the solution is to use an async function with await, but this is very new to me.

Can anyone provide some help on how to wait for a nested promise to finish, before iterating the .forEach loop?

Is the solution to make the .doSecondTask() function use async-await?

Alexis
  • 1,685
  • 1
  • 12
  • 30
Cody Pritchard
  • 703
  • 2
  • 15
  • 31
  • 1
    `.forEach()` is not await-aware; it will just charge through the iterations. However `for{...}` is await-aware; an `await` in the loop will hold things up. – Roamer-1888 Jul 28 '20 at 21:40
  • You are using Angular, Angular is built on RxJs. It is frowned upon to mix RxJs and promises. You should learn RxJs inside out before you begin your Angular journey. – Adrian Brand Jul 28 '20 at 22:20
  • https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop – Bergi Jul 28 '20 at 22:59

1 Answers1

0

Seeing you are in Angular you should be working with observables. If your service returned observable instead of promises like they should be in Angular you could use an RxJs concatMap.

from(this.records).pipe(
  concatMap(record => this.myService.doFirstTask(record)),
  switchMap(resultOfDoFirstTask => this.myService.doSecondTask())
).subscribe(resultOfDoSecondTask => {
  // Fires once for each record
});

This is the RxJs way. We tend to shy away from using promises in Angular because it is built upon RxJs.

Here is a runnable snippet for a demo.

const { of, from } = rxjs;
const { concatMap, switchMap, delay } = rxjs.operators;

const records = [1,2,3,4,5,6];

const myService = {
  doFirstTask: val => of(`${val}: from first task`).pipe(delay(500)),
  doSecondTask: val => of(`${val}: from second task`).pipe(delay(500)),
};

from(records).pipe(
  concatMap(record => myService.doFirstTask(record).pipe(
    switchMap(resultOfDoFirstTask => myService.doSecondTask(resultOfDoFirstTask))
  ))
).subscribe(resultOfDoSecondTask => {
  console.log(resultOfDoSecondTask);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.6.0/rxjs.umd.min.js"></script>

If you don't want to refactor your services to return observables you can wrap them in a from to get an observable from a promise. from(this.myService.doSecondTask())

Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
  • Solution is fragile. If the two delays are changed from 500,500 to 1500,500 then only the timing is affected, however with 500,1500, the entire behaviour is affected. – Roamer-1888 Jul 29 '20 at 22:42
  • Thanks for pointing that out. I have nested the switchMap so the concatMap waits for it. – Adrian Brand Jul 29 '20 at 22:56