2

I'm trying to write a method that will poll for the status of an API operation, returning an observable that will emit the result of each status call (since it contains progress information I would like to display) until it gets a completion status, emits that status, and then it's done.

What I have so far is this:

pollStatus(identifier: string): Observable<OperationStatusResource> {
    const obs = interval(1000)
      .pipe(
        startWith(0),
        switchMap( () => this.apiService.operationStatus(identifier) ),
        takeWhile( (value, index) => { 
          return value.status != OPERATION_STATUS.COMPLETE;
        }, true)
      )

    return obs;
  }

which works, and I understand it, but I think I can do better and I'm struggling to figure out how. This requests a new status every second and cancels the previous one if it hasn't completed yet. I DO want to cancel previous requests any time I send a new one to ensure that I never emit out of order due to internet shenanigans, but:

  • I'd like the delay to apply starting after the response from the last request. So like req->resp->1000ms->req etc.
  • I'd like to time-out each request. So for example if a request hasn't returned a response after 5 seconds, I'll cancel it and try again

I'd also be happy with any other way of accomplishing the same idea of being patient if the response takes a while, but not willing to wait forever, and not requesting too rapidly if the response comes back quickly either.

Bonus points if I can add an overall failure condition or timeout like give up if you get 3 timeouts in a row or if the whole thing takes more than 5 minutes or if I get 3 non-200 codes back in a row.

Dale
  • 393
  • 1
  • 3
  • 15
  • 1
    "I do want to cancel previous requests" and "I want to make the next one only after the first one responded" do not really go together. If you wait for the response there's never a previous request to cancel. – Ingo Bürk Oct 26 '19 at 06:19
  • @IngoBürk I want to wait X seconds and if the response doesn't come in that time, only then do I want to cancel it and send a new one. My concern is that if I poll every 500ms and it takes the server 600ms to respond, then I'll never get a response. – Dale Oct 28 '19 at 14:30

1 Answers1

2

After sleeping on it, I think that THIS answer with an added retry at the end is what I should use.

pollStatus(identifier: string): Observable<OperationStatusResource> {
  const obs = defer(() => this.apiService.operationStatus(identifier))
    .pipe (
      timeout(10000),
      repeatWhen(notification => notification.pipe(delay(1000))),
      takeWhile( (value, index) => { 
        return value.status != OPERATION_STATUS.COMPLETE;
      }, true)
    ).pipe(retry(1))
  return obs;
}

It will make the initial request, then wait for a response for a maximum number of ms (timeout) and after getting the response it will wait a number of ms (delay).

The retry at the end is what I needed. If the timeout is hit the request will be cancelled, and the retry will try again (once, in this example)

Dale
  • 393
  • 1
  • 3
  • 15