3

I have a polling use-case where:

  1. I want to call an API that, based on business logic, returns numbers(1-10) or error (network issue/ exception in API etc..) instantly (1.5-2 sec).
  2. If the API returns an error (network issue/ exception in API etc..) then I want to unsubscribe the polling and display error.
  3. If the API returns success, I want to check the return value and un-subscribe(if the return value is 5) or keep the polling going.
  4. I want to call API every 5 sec.
  5. I want to keep the max time(timeout/threshold) for the polling as 3mins. If I don't get the desired response(number 5) in these 3mins, the polling should error out.

This is how I have implemented it currently:

this.trackSpoke$ = interval(5000)
                .pipe(
                    timeout(250000),
                    startWith(0),
                    switchMap(() =>
                        this.sharedService.pollForAPropertyValue(
                            "newuser"
                        )
                    )
                )
                .subscribe(
                    (data: SpokeProperty) => {
                        this.CheckSpokeStatus(data);
                    },
                    error => {
                        this.trackSpoke$.unsubscribe();
                        this.createSpokeState.CdhResponseTimedOut();
                    }
                );


private CheckSpokeStatus(data) {
        if (data.PropertyValue === "5") {
            this.trackSpoke$.unsubscribe();
            //display success   
        } else {
              //keep the polling going
        }
    }

But, the above implementation is not timing out.

What needs to be done so that it times out and I am able to achieve all mentioned use-case?

chirag_lad
  • 229
  • 1
  • 16

2 Answers2

5

First of all using interval for API polling is quite an anti-pattern because interval won't "wait" for your http request to finish - potentially triggering multiple requests (if the request takes more then 5s to complete).

I prefer to use defer with repeatWhen and delay (see the code below).

The timeout is not triggering because the interval is ticking every 5s preventing the timeout from ever occurring. The defer/repeatWhen combo should also fix that.

Instead of manually unsubscribing consider using takeWhile to unsubscribe the Observable for you.

Also using this.trackSpoke$.unsubscribe(); in the error handler is not needed because Observable is unsubscribed automatically in case of an error.

this.trackSpoke$ = defer(() => this.sharedService.pollForAPropertyValue("newuser"))
    .pipe(
        timeout(250000),
        repeatWhen(notifications => notifications.delay(5000)),
        takeWhile(data => this.CheckSpokeStatus(data)),
    )
    .subscribe(
        error => {
            this.createSpokeState.CdhResponseTimedOut();
        }
    );


private CheckSpokeStatus(data) {
    return data.PropertyValue !== "5";
}
m1ch4ls
  • 3,317
  • 18
  • 31
0

I had a pretty similar use case.

written with rxjs 7.8.1

from(fakeDelayedRequest()).pipe(
  map((response) => {
    if (isValidResponse(response)) {
      return response;
    } else {
      throw `Not a valid response`;
    }
  }),
  retry({
    // getRetryDelayTimeOrThrow decides if we retry or not, depending on the error
    //  - returns an observable that triggers when the next request shall be sent
    //  - or throws the error which leads to exiting the retry loop
    delay: getRetryDelayTimeOrThrow,
  }),
  timeout({
    each: maxTimeout,
    with: () => throwError(() => 'TIMEOUT'),
  })
);

function getRetryDelayTimeOrThrow(e: any, retryCount: number) {
  console.error(e);

  if (repeatAfterError(e)) {
    return timer(getPollInterval(retryCount));
  } else {
    throw e;
  }
}

You can find the full working code in this stackblitz

andymel
  • 4,538
  • 2
  • 23
  • 35