Want to subscribe API call in loop

Viewed 1742

I am writing to subscribe to an API call in a loop. But I want the next iteration of the loop to start when the data of the previous call is received.

I am writing the below code but it initiated 10 call at the instant. But I want one by one, second when first is complete.

My Code:

for(let i=0; i<10 ;i++){
     this.service.getData().subscribe(data=>{
          console('data received for:',i)
     });
}
4 Answers

You can try that way , after the data is received you call the function again , it will continue till counter is smaller than 10 global variable:

counter = 1;

exampleFunction(){

if(this.counter < 10){
 this.service.getData().subscribe(data=>{
      if(data){
       console('data received for: counter)
       this.counter++
       this.exampleFunction()
       }  
     });
}
}

There are multiple ways to achieve this. It depends on your requirement. If the result of the subsequent requests depend on the response of the first, then you need to use any of the RxJS higher order mapping observables.

If not, then you could use RxJS from function to emit from an array of your requests and map it to emit sequentially. Try the following

from(Array(10).fill(this.service.getData())).pipe(
  concatMap(request => request)
).subscribe(
  res => { },
  err => { }
);

// `Array(10).fill(this.service.getData())` produces `[this.service.getData(), this.service.getData(), ...]`

Edit: The of function isn't required since the request is already an observable.

Working example: Stackblitz

Objective :

  1. To call an Api repetitively at defined intervals(delay/gap).
  2. We want to fetch the data when the previous request has finished.
  3. If previous request fails, we still want to retry 2/3 time (n number of times)

Problem with other given solutions:

All given answers are solving the first objective. BUT NOT THE REST. In a real world scenario we don't want to burden the server (server might take 3-4 seconds to respond but we still call api at fixed interval which is bad ) and we also want to write defensive code on frontend ( if server fails to respond , we still want to retry).

Solution:

Here is the complete working code

    import { of, concat, Observable, BehaviorSubject } from 'rxjs';
    import { delay, tap, skip, concatMap, map, retry } from 'rxjs/operators';
    import { HttpClient } from '@angular/common/http';


    @Component({
      selector: 'app-comp',
      template: ` <div>{{polledApi$ | async}}</div> `,
      styleUrls: ['./comp.component.scss']
    })
    export class CompComponent implements OnInit {

      api$ = this.http.get('https://blockchain.info/ticker');

      load$ = new BehaviorSubject('');

      polledApi$: Observable<number>;

      restart$ = of('').pipe(
        delay(5000), // wait for 5sec
        tap(_ => this.load$.next('')), // calls load$
        skip(1),
      );

      constructor(private http: HttpClient) { }

      ngOnInit(): void {

        this.polledApi$ = this.load$.pipe(
          concatMap(_ => concat(this.api$, this.restart$)), // restart only when api gives response
          map((response: any) => response.EUR.last), // Just a map operator for this example.
          retry(3) // retry 3 times in case of error
        );
      }

    }

Explanation:

Here we have 4 Observables

  1. api$ It defines api endpoint.
  2. load$ it is a BehaviourSubject which kind of glues all observables together.
  3. restart$ used only to call load$ after 5sec delay. it itself don't do anything.
  4. polledApi$ The ACTUAL ONE which we subscribe to.

A complete explanation of all operators like concat, tap might not be possible here. This code covers around all practical issues regarding polling.

Convert the observable into promise and wait for that:

async someMethod() {
  for (let i = 0; i < 10; i++) {
     const data = await this.service.getData().toPromise();
     console.log('data received for:',i);
  }
}

Working example here:

https://stackblitz.com/edit/angular-ivy-3tqnf6

Related