1

I'm trying to cache http calls in the service so all subsequent calls returns same response. This is fairly easy with shareReplay:

data = this.http.get(url).pipe(
    shareReplay(1)
);

But it doesn't work in case of backend / network errors. ShareReplay spams the backend with requests in case of any error when this Observable is bound to the view through async pipe.

I tried with retryWhen etc but the solution I got is untestable:

data = this.http.get(url).pipe(
    retryWhen(errors => errors.pipe(delay(10000))),
    shareReplay(1)
);

fakeAsync tests fails with "1 timer(s) still in the queue" error because delay timer has no end condition. I also don't want to have some hanging endless timer in the background - it should stop with the last subscription.

The behavior I would like:

  1. Multicast - make only one subscription to source even with many subscribers.
  2. Do not count refs for successful queries - reuse same result when subscriber count goes to 0 and back to 1.
  3. In case of error - retry every 10 seconds but only if there are any subscribers.
Adassko
  • 5,201
  • 20
  • 37

1 Answers1

0

My 2 cents:

  • This code is for rxjs > 6.4 (here V6.6)
  • To use a shared observable, you need to return the same observable for all the subscribers (or you will create an observable which has nothing to share)
  • Multicasting can be done using shareReplay and you can replay the last emitted value (even after the last subscriber to have unsubscribed) using the {refCount: false} option.
  • As long as there is no subscription, the observable does nothing. You will not have any fetch on the server before the first subscriber.

beware:

If refCount is false, the source will not be unsubscribed meaning that the inner ReplaySubject will still be subscribed to the source (and potentially run for ever).

Also:

A successfully completed source will stay cached in the shareReplayed observable forever, but an errored source can be retried.

The problem is using shareReplay, you have to choose between:

  • Always getting the last value even if the refCount went back to 0 and having possible never ending retries in case of error (remember shareReplay with refCount to false never unsubscribes)
  • Or keeping the default refCount:true which mean you won't have the second "first subscriber" cache benefit. Conversely the retry will also stop if no subscriber is there.

Here is a dummy example:


class MyServiceClass {

  private data;

  // assuming you are injecting the http service
  constructor(private http: HttpService){
      this.data = this.buildData("http://some/data")
  }

  // use this accessor to get the unique (shared) instance of data observable.
  public getData(){
    return this.data;
  }

  private buildData(url: string){
    return this.http.get(url).pipe(
      retryWhen(errors => errors.pipe(delay(10000))),
      shareReplay({refCount: false})
    );
  }
}

Now in my opinion, to fix the flow you should prevent your retry to run forever, adding for instance a maximum number of retries

Flavien Volken
  • 19,196
  • 12
  • 100
  • 133