First_Strike
First_Strike

Reputation: 1089

Rxjs - How to retry an errored observable while informing UI of the error

Problem

Suppose there is a Http request observable that errored, we can just retry it. But I also want the UI to inform the user that this resource failed to load. What is the best architecture?

Intended Behavior for the Target Observable

  1. Retry-able.
  2. Long-running. Doesn't complete or error.
  3. Shared. Does not generate unnecessary requests when multiple subscriber.
  4. Load on need. Does not generate unnecessary requests when not subscribed.
  5. Inform UI of the errors.

(3 and 4 can be achieved by shareReplay({bufferSize: 1, refCount: true}))

My Attempts

I think it's best to pass an error message to the downstream observer while keeping retrying the source. It causes minimum changes to the architecture. But I didn't see a way I can do it with Rxjs, because

  1. retry() always intercepts the error. If you materialze the error, then retry() won't retry. If not, then no error will propagate to the downstream.
  2. catchError() without rethrowing will always complete the stream.

Although let the UI observer tap(,,onError) and retry() can satisfy this need, but I think it is dangerous to let the UI take this responsibility. And multiple UI observer means a LOT of duplicated retries.

Upvotes: 3

Views: 1934

Answers (2)

First_Strike
First_Strike

Reputation: 1089

Well, I seem to have accidentally find the answer while browsing through the documentations.

It starts with the usage of the second parameter of the catchError. According to the documentation, retry is implemented by catchError. And we can express more logic with the lower-level catchError.

So it's just

catchError((err, caught) => {
  return timer(RETRY_DELAY_TIME).pipe(
    mergeMap(() => caught)
    startWith(err)
  );
})

It retries the observable, meanwhile sending error messages to the downstream observers. So the downstream is aware of the connection error, and can expect to receive retried values.

Upvotes: 3

Will Alexander
Will Alexander

Reputation: 3571

It sounds like you're looking for something akin to an NgRx side effect. You can encase it all in an outer Observable, piping the error handler to the inner Observable (your HTTP call), something like this:

const myObs$ = fromEvent('place event that triggers call here').pipe(
               // just one example, you can trigger this as you please
  switchMap(() => this.myHttpService.getResource().pipe(
    catchError(err => handleAndRethrowError()),
    retry(3)
  ),
  shareReplay()
);

This way, if the request throws an error, it is retried 3 times (with error handling in the catchError block, and even if it fully errors out, the outer Observable is still alive. Does that look like it makes sense?

Upvotes: 1

Related Questions