Farnoosh
Farnoosh

Reputation: 197

Utilizing rxjs repeatWhen in http request pipe in order to repeat the request if desired response is not returned

I'm trying to use below simplified version of my http request pipeline to ensure that if my response does not have the required data in res.myCondition to utlize repeatWhen and make another call, but I'm clearly not utilizing repeatWhen the way it's meant to be. (angular 8/ rxjs 6.5)

Intention is for the first call to come into the map, where I check the returned data, if the data is there I'll return it to the subscriber back in my component, but if myCondition is missing from res I'd like to repeat the api call a pre-determined number of times stored in reties.

any thoughts on where I'm going wrong with my repeatWhen implementation?

method1:

return this.http.get()
   .pipe(map(res => res.myCondition ? res : null),
        repeatWhen(res => retries-- > 0 ? of(res) : res), 
        catchError((error: HttpErrorResponse) => (error))

Upvotes: 4

Views: 4432

Answers (1)

kruschid
kruschid

Reputation: 779

Let's break down the description of the repeatWhen operator:

Returns an Observable that mirrors the source Observable with the exception of a complete. If the source Observable calls complete (1), this method will emit to the Observable returned from notifier. If that Observable calls complete or error (2), then this method will call complete or error on the child subscription. Otherwise this method will resubscribe to the source Observable (3).

(1): I can assume that the source observable this.http.get() will call complete after the get request finishes, right? So the repeatWhen operator is being notified/invoked eventually.

(2): if you reached your number of allowed retries then you don't want to retry anymore, right? So you will have to return an empty stream (an observable that doesn't emit anything) from the anonymous function (notifier) inside repeatWhen

(3): otherwise just return an observable that emits one single value to indicate that you want to retry (resubscribe) the get request

So the improvement you are probably looking for is the following (edit: see below why this answer is wrong):

return this.http.get()
   .pipe(
        mergeMap(res => res.myCondition ? of(res) : EMPTY), // (*)
        repeatWhen(() => retries-- > 0 ? of(true) : EMPTY), 
        catchError((error: HttpErrorResponse) => (error)
   )

(*) Please notice that I also replaced your map operator with mergeMap to prevent the stream from emitting null values. If your condition is not met it returns an empty observable in order to emit nothing instead.

edit:

Okay so I was wrong. After trying the code above in a rxjs playground I noticed that the repeatWhen operator is not calling the anonymous function for each value emitted by the source observable. Instead the anonymous function is called only once an has to return an observable that is based on the notifications observable that it receives as an parameter:

let finished = false;
return this.http.get()
   .pipe(
        mergeMap(res => {
            finished = retries-- <= 0 || res.myCondition;
            return finished ? of(res) : EMPTY;
        }),
        repeatWhen((notifications) =>
            notifications.pipe(
                takeWhile(() => !finished)
            )
        ),
        ...
   );

The weird part is that that notifications subject doesn't emit anything other than undefined. For each emission from the source observable (http.get) it gives you just undefined. So we could imperatively mutate a finished flag to control the repeatWhen operator. I found this workaround in a different question on SO: RxJS repeat depending on emitted value

Upvotes: 4

Related Questions