Loading Posts...

Retry failed HTTP requests in Angular

Retry failed HTTP requests in Angular

Immediate retry, delayed retry and retry with backoff

Accessing data from a backend is the backbone of almost every single page application. All dynamic content is loaded from a server.

In most cases, those HTTP requests work well and return the desired result. However, there are scenarios where a request might go wrong.

Imagine somebody is using your website over a hotspot on a train that rushes with 200 km/h across the country. 🚅 Well, the network might be slow, and the HTTP request still returns the desired result.

But what if the train enters a tunnel? The chances are high that the user has lost connection and your web app is unable to reach the server. In this case, the user is forced to refresh your application once he exits the tunnel and is back online.

Refreshing the application can have an impact on the current state of the application. The user could, for example, lose data he entered in a form.

Instead of letting a request instantly fail, it would be better to retry it a couple of times and display a corresponding message to the user. With such an approach, a refresh can be prevented.

Retrying failed requests

Let’s simulate the train situation with a sample backend that fails on the first three attempts and returns the data on the fourth attempt.

So usually, in Angular, we create a service, inject the HttpClient and use it to get data from the backend.

Nothing fancy here. We inject Angulars HTTPClient and perform a simple get Request. If this request returns an error, we perform some error logic and return an empty observable to tell the caller; An error occurred, but it’s okay, I got this.

This is how most application make their HTTP request. The implementation above executes the request once. It then either returns the data from the backend or fails.

So how are we going to retry our request if the greeting endpoint is not available or returns an error? Maybe RxJS provides an operator? Of course, it does, RxJS has an operator for everything 😉

The first thing that may come to our mind is the retry operator. Let’s have a look at its definition.

Returns an Observable that mirrors the source Observable with the exception of an error. If the source Observable calls error, this method will resubscribe to the source Observable for a maximum of count resubscriptions (given as a number parameter) rather than propagating the error call.

This sounds pretty much like the operator we need. So let’s pipe it in our chain.

We successfully applied the retry operator. Let’s check out how he affected the behavior of an HTTP request when it is used inside a sample application.

The application is super simple. It just performs an HTTP call once we click on the “PING THE SERVER” button.

As mentioned before the backend returns an error on the first three attempts and delivers the response on the fourth attempt.

In the network tab of the dev tools, we can see that the retry operator does its job and retries failed requests for three times. On its last attempt, he receives the response and reflects it to the UI.

That’s pretty cool. So we got ourselves a retry behavior. 🤘

But there is still room for improvement. Notice that the retries are immediately executed. This is not very helpful if we think about our tunnel example. 🤔

Delayed retries

We do not immediately exit a tunnel after we entered it. We spend some time in it. So, we need to stretch the retry period by delaying each attempt.

To achieve that we need more fine-grained control over the retry behavior. We need to be able to decide when retries should be executed. The retry operator is not enough. Let’s again consult the RxJs docs for help.

There’s a retryWhen operator which seems to suit our case. The official docs describe this operator in the following way:

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

What? 😶 Sounds pretty complicated. Let’s try to explain it in a more straightforward approach.

The retryWhen operator accepts a callback that returns an Observable. The returned Observable decides the actual behavior of the retryWhen operator based on the following rules:

The retryWhen operator …

  • retries the original observable if the returned observable emits successfully
  • gives up and emits an error once the returned observable emits an error
  • completes if the returned observable completes

The callback itself is only called when the source observable fails for the first time.

We can now leverage this knowledge to write a delayed retry with Rx’s retryWhen operator.

If the source Observable, which is our HTTP request, fails, the retryWhen operator is called. Inside the callback, we get access to the error that caused the failure. We delay the errors, decrement the number of retries and return a new Observable that emits the error.

Based on the rules of the retryWhen operator, this Observable kicks of an actual retry because it emits a value. If retrying fails for a couple of times and the retries variable goes down to 0 we finally give up and throw the exact error.

🤠 Cool! So, we could now take the snippet above and swop it with the retry operator in our chain. But wait.

What about the retries variable? This variable contains the current state of the retries. Where is it declared? When is the state reset? The state should be managed inside your stream and not outside.

Creating a custom operator named delayedRetry

By extracting the code above into a separate RxJS operator, we solve that state problem and furthermore improve the readability of our code.

There are different ways to implement your own RxJS operator. The methods on how to do it strongly depends on how your operator is built.

Our operator is built with existing RxJS operators. Therefore we can take the easier path of creating our RxJS operator. In our case, an RxJS operator is just a function with the following signature.

const customOperator = (src: Observable) => Observable

The operator accepts a source observable and returns another observable.

Since our custom operator allows the user to decide in which interval and how often retries should happen we need to wrap the function definition above in a factory function that accepts the delayMs and the maxRetry as parameters.

const customOperator = (delayMs: number, maxRetry: number) => {
return (src: Observable
) => Observable
}

⚠️ If you want to create an operator that does not consists out of existing operators you should pay attention to error and subscription handling. Furthermore youe should extend the Observable class and implement the lift function. Find out more if you are curious about this.

So, based on the snippet above — let’s write our custom Rx operator.

Neat. This operator is now available and can be imported. Let’s use our brand new operator in our HTTP request.

We put the delayedRetry operator to our chain and pass 1000 and 3 as parameters. The first parameter specifies the delay in milliseconds between each retry. The second one determines the max amount of retries.

Let’s restart our application and have a look at our operator in action.

We can see that each retry is delayed and the correct response is displayed to the user once the response arrives.

Follow me on Twitter or medium to get notified about my newest blog posts!🐥

Retry, back-off, repeat!

Let’s take the ideas from delaying requests one step further. In the previous attempt, we always delayed each request at the same time.

In this scenario, we increase the delay after each retry. The first retry happens after one second, the second one after two seconds and the third one after three seconds.

Let’s create a new retryWithBackoff operator which implements this behavior.

If we now start up our application, we can see how the dealy of retrying the requests increases.

After each retry, we back off and increase the delay. Once the source returns a correct response, we display it in the UI.

Conclusion

Retrying HTTP requests makes our application more stable.

It is especially useful to apply in critical requests which get data that is required for your application to work. For example configuration data that includes URLs of backends, we need to call.

In most scenarios, the retry operator from RxJS is not enough as it immediately retries failed requests. The retryWhen operator gives us more fine-grained control over retries. It lets us define when a retry should happen. With this power, we can implement delayed retries or retries with a backoff.

When implementing reusable behavior in your RxJS chain, it is recommendable to extract the logic in a new operator.

🧞‍ 🙏 If you liked this post share it and give some 👏🏻 by clicking multiple times on the clap button on the left side.

Feel free to check out some of my other articles about frontend development.


Retry failed HTTP requests in Angular was originally published in Angular In Depth on Medium, where people are continuing the conversation by highlighting and responding to this story.

Get Free Email Updates!

Signup now and receive an email once I publish new content.

I agree to have my personal information transfered to MailChimp ( more information )

I will never give away, trade or sell your email address. You can unsubscribe at any time.

user

The author didnt add any Information to his profile yet

Loading Posts...