Dexter
Dexter

Reputation: 528

Unable to set interval up to max 2 min or until condition met using angular

here what my scenario is i have 2 api's apiOne and apiTwo and when ever i call the apiOne is should give response and if the response is success then i have to send this repsonse to apiTwo as param then apiTwo will give another response in that i may get like "created" ,"in_progress" . here the issue is

  1. How can i call the apitwo using interval for every 3 seconds until i get the response as "in_progress" and if i didnt get the response as like above then i need to poll the apiTwo till max 2 min and cancel the call. if i get the response as in_progress then i need to stop the interval or max 2 min cancel the interval or subcription.

  2. I already wrote the code in nested way but it is not efficient .

below is my code

 initiate() {
    this.showProgress = true;
    
    const data = {
      id: this.id,
      info: this.Values.info,
    };

    // First Api call
    
    this.userServ.start(data).subscribe(res => {
     
      this.Ids = res['Id'];
      if (this.Ids) {
      
      
      // Second Api call
      
        this.Service.getStatus(this.Ids).subscribe(resp => {

          if (resp) {
            
            this.Status = res['Status'];
            
            // if resp is In_Progress
            
            if (this.Status === 'In_Progress') {
              this.Start();
            } else {

            // if resp is not In_Progress then i get the response i am calling the api
            
              this.intervalTimer = interval(3000).subscribe(x => {

                this.Service.Status(this.Ids).subscribe(ress => {
                 
                  this.Status = ress['Status'];
                  if (this.Status === 'In_Progress') {
                    this.delayStart();
                    this.intervalTimer.unsubscribe();
                  }
                });
              });

            }
          }

        }, err => {
          console.log(err);
        });
      }

    }, error => {
      console.log(error);
    });

  }

Upvotes: 2

Views: 677

Answers (2)

evilstiefel
evilstiefel

Reputation: 496

I'd use expand from rxjs, which will pass through the result of the source observable, but also let's you act according to the content of the result.

Also, avoid nesting calls to subscribe whenever possible. Consider this example code for reference:

this.userServ.start(data).pipe(
  // use switchMap to not have 'nested' subscribe-calls
  switchMap((result) => {
    if (result['Id']) {
      // if there is an ID, ask for the status
      return this.Service.getStatus(result['Id']).pipe(
        // use the expand operator to do additional processing, if necessary
        expand((response) => response['Status'] === 'In_Progress'
          // if the status is 'In_Progress', don't repeat the API call
          ? EMPTY
          // otherwise, re-run the API call
          : this.Service.getStatus(result['Id']).pipe(
            // don't re-run the query immediately, instead, wait for 3s
            delay(3000)
          )
        ),
        // Stop processing when a condition is met, in this case, 60s pass
        takeUntil(timer(60000).pipe(
          tap(() => {
            // handle the timeout here
          })
        ))
      );
    } else {
      // if there is no ID, complete the observable and do nothing
      return EMPTY;
    }
  }),
  /**
   * Since expand doesn't filter anything away, we don't want results that
   * don't have the status 'In_Progress' to go further down for processing
   */
  filter((response) => response['Status'] === 'In_Progress')
).subscribe(
  (response) => {
    this.Start();
  }, (error) => {
    console.log(error)
  }
);

Upvotes: 1

Owen Kelvin
Owen Kelvin

Reputation: 15098

You may consider using the below approach See Code on Stackblitz

  id = 1;
  Values = { info: true };
  get data() { return { id: this.id,info: this.Values.info}}
  showProgressSubject$ = new BehaviorSubject(true);
  showProgressAction$ = this.showProgressSubject$.asObservable();
  currentStatusSubject$ = new Subject<string>();
  currentStatus$ = this.currentStatusSubject$.asObservable()
  stoppedSubject$ = new Subject();
  stopped$ = this.stoppedSubject$.asObservable();
  startedSubject$ = new Subject();
  started$ = this.startedSubject$.asObservable();
  interval = 500; // Change to 3000 for 3s
  maxTrialTime = 6000;// Change to 120000 for 2min
  timer$ = timer(0, this.interval).pipe(
    tap((i) => {
      if(this.maxTrialTime/this.interval < i) { this.stoppedSubject$.next()}
    }),
    takeUntil(this.stopped$),
    repeatWhen(() => this.started$)
  )
  apiOneCall$ = this.userServ.start(this.data);
  apiTwoCall$ = this.apiOneCall$.pipe(
    switchMap(({Id}) => Id ? this.Service.getStatus(Id): throwError('No Id')),
    tap((res) => this.currentStatusSubject$.next(res)),
    tap(res => console.log({res})),
    tap((res) => {if(res === 'created') {this.stoppedSubject$.next()}})
  )
  trialCallsToApiTwo$ = this.timer$.pipe(mergeMap(() => this.apiTwoCall$))

In your Html you can use the async pipe

Show Progress : {{ showProgressAction$ | async }} <br>
Timer: {{ timer$ | async }}<br>

Response: {{ trialCallsToApiTwo$ | async }}<br>

<button (click)="startedSubject$.next()">Start</button><br>
<button (click)="stoppedSubject$.next()">Stop</button><br>

Explanation

We begin by setting up the properties id, Values and data being a combination of the 2 values

 id = 1;
 Values = { info: true };
 get data() { return { id: this.id,info: this.Values.info}}

We then create a Subject to help with tracking of the progress of the operations. I am using BehaviorSubject to set the initial value of showing Progress to true.

We will use currentStatus$ to store whether current state is 'in_progress' or 'created'

stopped$ and started will control our observable stream.

You may have a look at the below post What is the difference between Subject and BehaviorSubject?

showProgressSubject$ = new BehaviorSubject(true);
showProgressAction$ = this.showProgressSubject$.asObservable();
currentStatus$ = this.currentStatusSubject$.asObservable()
stoppedSubject$ = new Subject();
stopped$ = this.stoppedSubject$.asObservable();
startedSubject$ = new Subject();
started$ = this.startedSubject$.asObservable();

Next we define interval = 500; // Change to 3000 for 3s and maxTrialTime = 6000;// Change to 120000 for 2min

We then define a timer$ observable using the timer operator. The operator is used to generate a stream of values at regular interval

We set the delay to 0 and the interval to interval property we had earlier created

We then tap into the observable stream. The tap operator allows us perform an operation without changing the observable stream

In our tap operator, we check whether the maximum time has been reached and if it has we call the next function on stoppedSubject$. We pipe our stream to takeUntil(this.stopped$) to stop the stream and repeatWhen(() => this.started$) to restart the stream

  timer$ = timer(0, this.interval).pipe(
    tap((i) => {
      if(this.maxTrialTime/this.interval < i) { this.stoppedSubject$.next()}
    }),
    takeUntil(this.stopped$),
    repeatWhen(() => this.started$)
  )

The Remaining part is to make a call to the apis We will use switchMap to combine the two observables. switchMap will cancel any earlier request if a new request is made. If this is not your desired behaviour you may consider exhaustMap or the mergeMap operators

From the result of apiOneCall$ if no id, we use the throwError operator to indicate an error otherwise we return a call to apiTwo

We tap into the result of apiTwoCall$ and call the next function on currentStatusSubject$ passing in the response. This sets the value of currentStatus$ to the result of the response

The line tap((res) => {if(res === 'created') {this.stoppedSubject$.next()}}) taps into the result of apiTwoCall$ and if it is 'created' it stops the timer

apiOneCall$ = this.userServ.start(this.data);
apiTwoCall$ = this.apiOneCall$.pipe(
  switchMap(({Id}) => Id ? this.Service.getStatus(Id): throwError('No Id')),
  tap((res) => this.currentStatusSubject$.next(res)),
  tap(res => console.log({res})),
  tap((res) => {if(res === 'created') {this.stoppedSubject$.next()}}) 
)

Now we finally combine the timer$ and apiTwoCall$ with mergeMap operator trialCallsToApiTwo$ = this.timer$.pipe(mergeMap(() => this.apiTwoCall$))

In Our HTML we can then use the async pipe to avoid worrying about unsubscribing

{{ trialCallsToApiTwo$ | async }}

Upvotes: 2

Related Questions