In this post I will show how to retry http requests using RxJs.
My first introduction to RxJs was through Angular’s http implementation. I was initially very skeptical of the move to RxJs as a core piece of the http module.
In fact I raised a Github issue on this topic very early on.
Since then I have moved on to agreement, with a touch of doubt, when it comes to the decision to move to RxJs.
It’s not that I am a doubter of RxJs as a library. It’s more that I think the benefits are sometimes exaggerated when compared to promise based counterparts – especially in the context of http.
Not to mention the overhead involved in training people to successfully use RxJs.
This however is a gift to people who offer training courses since it definitely increases the demand for their material. I guess I should be grateful for this since some of my most visited articles are about RxJs :-)
The way I see it, there are two major selling points for RxJs based http.
The first, and perhaps the most important benefit, is the ability to cancel a request in “flight”. I talk a little bit about that in one of my other articles.
Of course when I say “cancel”, I am not necessarily talking about preventing the request from hitting the server. “Cancellation” generally means preventing processing of the http response on the client. This is still a huge benefit though. I can actually be incredibly helpful in preventing client side bugs from your ajax requests returning out of order.
Most likely, built-in support for [http] cancellation is probably what made RxJs extra appealing for the Angular http module.
Another benefit is the ability to retry a failed request.
Most of the time you don’t need retry logic for http request, but when you do, RxJs makes this very easy.
Retry in it’s simplest form can be achieved by adding the “retry” operator. Essentially all you have to do is tack on a retry(n) call to the http operation as seen below:
Here the http request is retried up to five times in case of failure. If all calls end in failure, you will have made the call six times – the initial call + five retries.
While this is simple, it’s probably not practical. All calls will be made in rapid succession, without delay. Not to mention, it will retry the call regardless of error type.
It might be tempting to add the delay operator here, but that will not have any effect on delaying each retry.
To address the shortcomings of the simple retry, I have to switch to the more flexible retryWhen operator.
The retryWhen operator is actually very flexible since it allows you to manage the retry pipeline as an observable, with all the power the RxJs toolbox has to offer.
Let’s modify the original example to use retryWhen.
From retryWhen I am returning a new observable with lot’s of bells and whistles.
Not only can I delay retries, I can also filter them down to specific error scenarios. In this case I am only interested in retrying if the http status code is 503.
The number of retries is controlled by the take operator.
There is also an interesting “trick” at the end with the concat operator. This allows me to combine the retry observable with a final error observable.
When all retries have been exhausted, the throw observable will fire, and cause the error to bubble up to the subscriber’s error handler.
The reason concat works well here is because it is designed to wait for the first observable (retry observable) to complete before emitting any values.
For more details, I have another article about combining observables here.
I also have a different article where I detail how to unit test the conditional retry logic in this example.