DEc0y
DEc0y

Reputation: 13

When using subscribe inside a while loop, it should wait till subscribe code is complete before next iteration as condition depends on each response

I am currently subscribing to an observable inside a while loop. The condition for while loop is a flag, whose value is updated based on some values from the current response. However the while loop doesn't wait for the flag to be updated (or code inside subscribe to be executed completely), and hence the loop runs infinitely.

The requirement is to continuously make new POST request as long it return a response object which has a field "continue" as "true" and each request should be made using the updated values for varA and varB from current response.

For e.g. if after 4 subscribe and corresponding responses, if 5th response has {continue: "false"} then the loop should terminate immediately making total iterations

So basically the loop should terminate immediately when res['continue'] is not "true". However, in my case the loop is executing infinitely and the value for testLoop is NOT becoming false. The loop is NOT updating the value for varA and varB in each iteration as well. Basically the loop does not wait till subscribe code completes execution.

Method in component.ts file

let testLoop = true;
let varA = 100;
let varB = 0;
while(testLoop){
    this.someService.sampleFunc(varA, varB).subscribe((res:any)=>{
        //do something with res
        if(res['continue'] == 'true'){
            varA = res['A'];
            varB = res['B'];
        }
        else{
            testLoop = false;
        }
    })
}

Method in service.ts file

sampleFunc(varA: number, varB: number){
    return this.http.post(url,{
        varA: varA,
        varB: varB
    })
}

sample response from POST method

{
    count: 100,
    varA: 10,
    varB: 10,
    continue: "true"
}

I had gone through multiple threads suggesting different rxjs operators, but could not find a solution which suits the above mentioned use case.

There was requirement which was missed. From each subscribe result, I also need to append to an array finalValues and use that array once the loop finshes executing.

The code in component.ts would now look like:

let testLoop = true;
let varA = 100;
let varB = 0;
let finalValues = [];
while(testLoop){
    this.someService.sampleFunc(varA, varB).subscribe((res:any)=>{
        finalValues = finalValues.concat(res['values']);
        if(res['continue'] == 'true'){
            varA = res['A'];
            varB = res['B'];
        }
        else{
            testLoop = false;
        }
    })
}

sample response

{
    values: ['A','B','C'],
    count: 100,
    varA: 10,
    varB: 10,
    continue: "true"
}

Upvotes: 1

Views: 777

Answers (3)

Eliseo
Eliseo

Reputation: 57981

  obs$ = this.sampleFunc(1,2)
  sampleFunc(a:number,b:number)
  {
    return this.sampleCall(a,b).pipe(
      tap((res) => {
        ..do something with "res"..
      }),
      switchMap((res:any) => {
        if (res.continue == 'true') return this.sampleFunc(res.a, res.b);
        return EMPTY;
      })
    );
  }
  sampleCall(a:number,b:number)
  {
      return this.http.post(url,{
        varA: varA,
        varB: varB
      })
  }

Then you simple subscribe to obs$, and don't forget unsubscribe!!

See stackblitz (in the stackblitz I use async pipe to subscribe/unsubscribe)

Update the instructions under tap are executed when the subscription is made, so in in res we have as response an object with a property "values" we can make some like

  finalValues:any[]=[];
  sampleFunc(a:number,b:number)
  {
    return this.sampleCall(a,b).pipe(
      tap((res) => {
        this.finalValues=this.finalValues.concat(res.values)
        //or
       this.finalValues=[...this.finalValues,...res.values]
      }),
      switchMap((res:any) => {
           ...
      })
    );
  }

Upvotes: 0

BizzyBob
BizzyBob

Reputation: 14750

You can use the expand operator to recursively call a method that returns an observable, then use takeWhile to stop calling when a condition is met.

Something like this should work for you:

repeatSampleFunc$ = this.someService.sampleFunc(varA, varB).pipe(
   expand(({A, B}) => this.someService.sampleFunc(A, B)),
   takeWhile(({continue}) => continue === 'true', true) 
);
repeatSampleFunc$.subscribe(
   response => // do something with response
);

To accumulate the results into a single array, you can use the reduce operator:

const EMPTY_RESPONSE = {
    values: [],
    count: 0,
    varA: 0,
    varB: 0,
    continue: "true"
};

repeatSampleFunc$ = this.someService.sampleFunc(varA, varB).pipe(
   expand(({A, B}) => this.someService.sampleFunc(A, B)),
   takeWhile(({continue}) => continue === 'true', true),
   reduce((finalValues, {values}) => finalValues.concat(values), this.EMPTY_RESPONSE)
);

Upvotes: 2

Jose
Jose

Reputation: 132

You can create a function to make the call and to handle the answer, if the condition to make a new request success it calls to itself again (recursively) if not, it ends.

keepAlive = () => {
  this.service.makePostRequest().subscribe(answer=>{
    if (!answer.condition) return
    this.keepAlive()
})

}

Upvotes: 0

Related Questions