Search code examples
angularrxjsrxjs5

rxjs publishReplay: reset on Error


I'm using publishReplay(1).refCount() to cache http requests in Angular5. This works fine - except in error-cases.

I'm swallowing and reporting exceptions from the http-source, that's why I pass Observable.of(undefined) in the code below. In that case, it caches undefined of course - but following subscribers shouldn't get that value, instead it should request against the http resource again, like there would be no cache.

My current code:

result: Observable<any>;
list() {

  if (this.result === undefined) {
    this.result = this.http.get(`https://swapi.co/api/people`)
      .catch(error => {
        console.error("Something really bad happened", error);
        return Observable.of(undefined);
        // in that case, reset publishReplay
      })
      .publishReplay(1)
      .refCount();
  }

  return this.result;
}

I have a stackblitz (with shareReplay, but the same problem) to show the behaviour: https://stackblitz.com/edit/angular-s5zuqa. A click on "Load" should re-request the http-source instead of providing the cached object from the catch.


Solution

  • So if I understand correctly, you're looking for this, right? There are two important things to note here:

    1. We use shareReplay(1) instead of publishReplay(1).refCount().
    2. The catch has been moved to behind the multicast operator.

    You'll see that the (faked) HTTP call errors the first time around, emitting undefined (since we catch it). The next time around, we run the request again, this time getting a result (42) which is cached for the third run, not causing another request.

    // Just to keep track of how often we sent the request already
    let counter = 1;
    
    // A fake HTTP request which errors the first time, then succeeds
    const request$ = Rx.Observable.of(null)
      .do(() => console.log('HTTP Request (#' + counter + ')!'))
      .delay(250)
      .switchMap(() => counter++ === 1 
        ? Rx.Observable.throw('Error!')
        : Rx.Observable.of(42)
      );
    
    // =========
    
    const result$ = request$
      .shareReplay(1)
      .catch(() => Rx.Observable.of(undefined))
    ;
    
    Rx.Observable.timer(0, 1000)
      .take(3)
      .do(() => console.log('Subscribing to result$…'))
      .switchMap(() => result$)
      .subscribe(value => console.log('Received value: ', value));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>