I have the following setup, which, every 3 seconds would make a new HTTP request to a server.
getData(param1: string): Observable<any> {
return timer(0, 3000).pipe(
switchMap(() => this.http.get(param1))
);
}
If a given request takes more than 3 seconds, the switchMap()
(I think) will cancel it and fire off a new one.
Now, I want to make it so that if a request is taking more than 3 seconds it waits for it to complete before firing off another one. Just for context, the idea is that if there's performance issues with the requests, my front-end is not stuck firing off and cancelling requests too early.
I somewhat got this to work with the following:
currentObs: Observable<any>;
getData(param1: string): Observable<any> {
return timer(0, 3000).pipe(
throttle(_ => this.currentObs),
switchMap(() => {
this.currentObs = this.http.get(param1)
return this.currentObs;
})
);
}
This will keep track of the currentObs
which is the observable of the current HTTP request. It then passes it to a throttle()
method so that the values from timer()
that normally prompt new requests are ignored until the request (currentObs
) completes.
This seems to work but it's a bit awkward as I'd need to keep some of the state outside the pipe()
. It's also a bit confusing because the throttling is based on an event that happens after it. I've been looking for a way to pass the result of the switchMap()
onto the throttle()
but first I didn't find one, and second, wouldn't that cause the throttle()
to be in the wrong side of the pipe?
Is there a neater way to achieve this using RxJS?
Edit:
With @Mrk Sef's answer for a more elegant solution and @kvetis' warning for handling errors, I ended up with the following pipe that will make a request, wait for 3 seconds after a success and then make another request. If the request fails, it's going to wait for 3 seconds and make another request. and then start from the top.
getData(param1: string): Observable<any> {
return this.http.get(param1).pipe(
repeatWhen(s => s.pipe(
delay(3000)
)),
retryWhen(s => s.pipe(
delay(3000)
))
);
}
ExhaustMap
Try to run
this.http.get
every 3 seconds, if the previous call isn't done within 3 seconds, do nothing and try again 3 seconds later.Repeat with Delay
Whenever the previous call ends, wait 3 seconds and then make the call again
Comparative Repeat
Repeat the call every 3 seconds, unless the call takes longer than 3 seconds in which case repeat the call as soon as the previous call ends.
This is closest to what you described. It works by using a silent timer to artificially "extend" the HTTP call. This works because
merge
won't complete until both inner observables complete. This means the fastest the merge will complete is 3 seconds.