0

I'm calling an API which runs in for loop, but we want to wait for 1-2 seconds before hitting the API everytime inside the loop, so we made the method as async and added await before hitting the apiMethod.

But I also want to call the async method after every 5 minutes, so I added the interval method just before closing the async method. But it behaves weirdly, sometimes it waits for 5 min before invoking the method again, sometimes it calls it multiple times, and sometimes before 5 min is over. Any help ?

async fetchData(){

for(let index=0;index<rollNo[].length; index++){ 

 await new Promise(r => setTimeout(r, 2000)); //delay of 2 sec before passing the next rollNo

//calls the service
  let  subscription = callAPIMethod(rollNo[index]).subscribe((data) => {
  this.Response = data;
  
  subscription.unsubscribe();

}, (error) => {
  this.showToasterMessage('', 'We encountered an error', 'error');
  return;
}); 
}


 //Wait for 5 min before starting all over again
 interval(300000).subscribe(x => {
      this.fetchData();
    });

}

I did try .pipe(delay(2000)) before making the method async but all it does is wait for 2 seconds and then run the entire for-loop at once which I don't want. Interval or setTimeOut works only when the method is not async, but if I don't make the method as async then I can't wait for 2 seconds before hitting the API everytime.

Drenai
  • 11,315
  • 9
  • 48
  • 82
Pankaj Kumar
  • 31
  • 1
  • 11
  • btw if you are searching on how to do this online include `rxjs` in your search terms as this is the library that angular uses for asynchronous calls and operations. – Igor May 10 '21 at 20:06
  • Why was this question closed? The question mentioned above doesn't answer the question I asked. – Pankaj Kumar May 10 '21 at 20:08
  • See the answers in the duplicates (outside of only the accepted ones). They include how to create a reoccurring timer to do something (your 5 minutes) and you already have your delay (2 seconds). You could put it all into a single operation instead of 2 calls but how you abstract the calls is up to you. – Igor May 10 '21 at 20:10
  • Please read the question. All those answers are for normal methods, not async. I want to call the async method again after certain interval which isn't working as its supposed to. Closing the question just because there is something similar is just not fair. – Pankaj Kumar May 10 '21 at 20:12
  • @Igor Show me one example where someone has called the async method again after a certain period of time or else let someone help me. – Pankaj Kumar May 10 '21 at 20:15
  • 1
    You could do `interval(300000).pipe(delay(2000)).subscribe(() => callAPIMethod().subscribe())`. But one of the issues is you are mix/matching rxjs with promise which can lead to bugs like not waiting for a result. – Igor May 10 '21 at 20:19
  • @Igor Using the above code means removing async and await., so there is no solution where I can wait for the result to come, still hit the API after delay and start over all again after certain interval of time ? – Pankaj Kumar May 10 '21 at 20:21
  • Your logic will add 2 seconds to my 5 minutes. I want to add 2 minutes to every call and once the for loop ends, go to sleep for 5 min and then start again., and yet this question is closed ? – Pankaj Kumar May 10 '21 at 20:23
  • The issue you have in your current code is that you call `this.fetchData();` but this returns a promise and you never do anything with that promise (like handle the exception or response) so the observed behavior can appear inconsistent. I can reopen your question but I would strongly advise you to look at a pure rxjs approach to solving this instead of trying to mix promise with rxjs. – Igor May 10 '21 at 20:24
  • I am handling the error and response, please check the description for updated code – Pankaj Kumar May 10 '21 at 20:26

2 Answers2

2

Sure you can get it using another approach and the timer rxjs/operator. An observable like

obs=timer(0,1000).pipe(
      take(this.rollNo[].length),
      switchMap(_=>{
      return callAPIMethod()
    }))

If you enclosed in another timer and use tap

timer(0,30000).pipe(switchMap(_=>{
  return this.obs.pipe(tap((res)=>{
      this.Response=res;
  }))
})).subscribe()

you get it

Updated I made a "fool example" in this stackblitz. You can see how the observable "callAPIMethod" is called so many times as elements has the array "values" each seconds, after 10 seconds start again (really as the "values" has 4 elements, after 7 seconds aprox)

Update 2 you has a code like

//this NOT WORK, callAPIMethod return null
//in general, when we work with observables only makes actions in subscribe or in tap 
//in the main "subscription" -in the example I use tap because I want to 
//execute actions in a "inner observable"
callAPIMethod(code) {
    let subscription = this.appService
      .slotByPin(code, this.date)
      .subscribe(data => {
        this.response = data;
        this.someOperationInsideThisMethod();
        subscription.unsubscribe();
      });
  }

But you should transform, your callAPIMethod should return an Observable, so transform the code by

this.obs = timer(0, 1000).pipe(
  take(this.parameters.length),
  switchMap(index => {
    //see that index is the result of "timer", that's 0,1,2,3...
    //so we can send to the callAPIMethod parameters[index]
    return this.callAPIMethod(parameters[index]);  
  })
);
timer(0, 10000)
      .pipe(
        switchMap(_ => {
          return this.obs.pipe(
            tap(
              data => {
        //see that you put here the code you had in your "callAPIMethod"
        this.response = data;
        this.someOperationInsideThisMethod();
            })
          );
        })
      )
      .subscribe();
  
  //see how the "callAPIMethod" return an observable
  callAPIMethod(code) {
    return this.appService
      .slotByPin(code, this.date)
      
  }
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Sorry, didn't get you. my callAPIMethod() accepts the argument from the for loop whch is essentially this.rollNo[index]. How do I pass the index now ? – Pankaj Kumar May 10 '21 at 20:56
  • Please answer properly, I can't understand where do you want me to write these 2 methods. everything inside for loop or the main method ? – Pankaj Kumar May 10 '21 at 20:59
  • I updated the answer with a "fool stackblitz", I hope this help to understand the code – Eliseo May 11 '21 at 07:46
  • to some extent yes, but in your 1st method you are not even subscribing to the callAPIMethod(), neither storing it's value to the result array. May be I am wrong but you are only doing this in 2nd method. am I right ? – Pankaj Kumar May 11 '21 at 10:07
  • What am I doing wrong here https://stackblitz.com/edit/angular-ivy-dushya Help me correct it – Pankaj Kumar May 11 '21 at 10:38
  • I think you got it wrong, the value array in your example is not the result I want to print in for loop, that value array is just a parameter array which gets passed to the api call after every 2 seconds – Pankaj Kumar May 11 '21 at 10:49
  • @PankajSingh, your "callAPIMethod" should return **an observable**, I update the answer to show how transform your code. the "trick" is that we can perform any action in "tap". – Eliseo May 11 '21 at 11:37
  • I want to perform `this.response = data; this.someOperationInsideThisMethod();` in first method as well, but you are only returning from the 1st call. So If I understand your code, then you are sending the 1st call, returning from it without saving the data in responseArray, and then you are waiting for 10 seconds to save data in response and perform some operation on it. What I want is. ` 1. Call the API with some parameter (parameter is an array). 2. Store the result somewhere. Repeat Step 1 & 2 until parameter.length Go to sleep for 5 min Repeat Step 1 & 2` – Pankaj Kumar May 11 '21 at 12:15
  • I solved it using my own logic, please have a look at it once if it is right according to u – Pankaj Kumar May 11 '21 at 15:19
-1

Solved with this

selectedCodes: string[] = [];

 ngOnInit() {
this.delayedLoop(this.selectedCodes.length, 0);


setInterval(() => {
  this.initializeSearch();
  this.delayedLoop(this.selectedCodes.length, 0);
}, ((5000+this.selectedCodes.length)*1000));


  delayedLoop(i:any,pIndex:any) {
    let arrLength =  i;
    let tempIndex = pIndex;
    if(this.selectedCodes && pIndex <  this.selectedCodes.length){
      let myThis = this;
      setTimeout(function() {
        myThis.fetchSlots(tempIndex,this.dateArrray);  
       
        if (--arrLength) {
          tempIndex++;
          myThis.delayedLoop(arrLength,tempIndex);
        }  
      }, 1000)
    }
  }
Pankaj Kumar
  • 31
  • 1
  • 11