### Our content is free thanks to ag-Grid

ag-Grid is the industry leading JavaScript datagrid

ag-grid.com

## Power of RxJS when using exponential backoff

Retrying on errors is a common practice, and RxJS has built-in operator for that. But what if we need to retry with exponential backoff strategy? Or have a better control when to retry? backoff-rxjs library has the answers.

Most of the modern-day Angular web apps make Ajax requests to the servers. These requests involve multiple network components (such as routers, switches, etc) as well as servers’ state and everything has to go just right for them to succeed. However, sometimes it doesn’t.

To handle such circumstances web apps typically implement retry logic that retries requests until they go through or until the maximum number of requests is reached. In most cases, simple retries are good enough to achieve the goal, but sometimes more advanced approach is needed.

### What’s exponential backoff?

Exponential backoff is an algorithm that uses exponentially longer delays between retries. In this article, I’ll dive deeper into two custom RxJS operators (both are part of `backoff-rxjs` package) that use exponential backoff and the use cases they cover:

• `retryBackoff`, operator that retries on errors
• `intervalBackoff`, operator that emits sequential numbers

#### Exponential function

I’ve used the term exponential a few times already, but what does it mean? In mathematics, it’s a function of the following form:

In our case, as new values are emitted (x in the function above) the longer the delay between them will be. In code it translates into the following method:

``````function calculateDelay(iteration, initialInterval) {
return Math.pow(2, iteration) * initialInterval;
}``````

With iterations starting from 0 and provided initial interval of 1000 milliseconds the emitted values would be 1000, 2000, 4000, 8000…

With that out of our way, let’s get into our first use case.

## Operator retryBackoff

One of the most frequent use cases for exponential backoff is to retry on error. A good example would be Google Cloud Storage (GCS) which requires this strategy to be used when retrying failed requests.

Before working on `backoff-rxjs` I found a few examples of exponential backoff retries in some gists or this stackoverflow answer, but none were flexible enough for my needs; thus I created `retryBackoff`.

`retryBackoff` takes either a number as initial delay or a`RetryBackoffConfig` for more configurations. RxJS uses marble diagrams to visualize how operator works, so here is one for our operator.

Notice how `retryBackoff` here behaves similarly to `retry` operator and can be as simple as:

``````message\$ = of('Call me!').pipe(
switchMap(() => this.service.callBackend()),
retryBackoff(1000),
);``````

### RetryBackoffConfig

When more customization is required `retryBackoff` operator takes `RetryBackoffConfig` that has the following shape:

``````export interface RetryBackoffConfig {
// Initial interval. It will eventually go as high as maxInterval.
initialInterval: number;
// Maximum number of retry attempts.
maxRetries?: number;
// Maximum delay between retries.
maxInterval?: number;
// When set to `true` every successful emission will reset the delay and the
// error count.
resetOnSuccess?: boolean;
// Conditional retry.
shouldRetry?: (error: any) => boolean;
backoffDelay?: (iteration: number, initialInterval: number) => number;
}``````

If, for example, we want to limit the number of retries up to twelve, our call would look like this:

``````message\$ = of('Call me!').pipe(
switchMap(() => this.service.callBackend()),
retryBackoff({
initialInterval: 100,
maxRetries: 12,
}),
);``````

Let’s look into the properties of `RetryBackoffConfig`

• `initialInterval` — initial delay that is also used to calculate all the rest of the delays; this is the only require property
• `maxRetries` — the maximum number of retries
• `maxInterval` — the maximum delay between retries
• `resetOnSuccess` — whether a successful response should reset both the count and the delay between errors to initial state (added as of version 6.5.6)
• `shouldRetry` — function that lets you analyze the error and decide whether to continue retrying (return `true`) or stop retrying (return `false`)
• `backoffDelay` — function for calculating custom delays

Following the addition of `resetOnSuccess` to RxJS's `retry` operator, Valentin Hăloiu contributed with similar flag for `retryBackoff` operator - it's very useful and we are considering making it a default behavior from the next major version.

The other two config functions (`shouldRetry` and `backoffDelay`) I think deserve a bit more info.

### shouldRetry function

Sometimes when we get onto particular error we would like to stop retrying, for example if the return status is 404, there is little chance that it will ever succeeds.

``````// Determine if the error matches our expected type
function isHttpError(error: {}): error is HttpError {
// This is a type guard for interface
// if HttpError was a class we would use instanceof check instead
return (error as HttpError).status !== undefined;
}

message\$ = of('Call me!').pipe(
tap(console.log),
switchMap(() => this.service.callBackend()),
retryBackoff({
initialInterval: INIT_INTERVAL_MS,
maxInterval: MAX_INTERVAL_MS,
resetOnSuccess: true,
shouldRetry: (error) => {
// error could be anything, including HttpError that
// we want to handle from sevice.callBackend()
if (isHttpError(error)) {
// If this is HttpError and status is not 404
// then continue retrying
return error.status !== '404';
}
// should retry for the rest of the types of errors.
return true;
},
}),
)``````

### backoffDelay function

By default delays will be doubled between each interval, however sometimes smoother backoff is needed. Through `backoffDelay` property we can provide a custom delay calculation function, e.g.:

`backoffDelay: (iteration, initialInterval) => Math.pow(1.5, iteration) * initialInterval` ,
or even slower increase of the delays
`backoffDelay: (iteration, initialInterval) => Math.pow(1.1, iteration) * initialInterval`

#### Demo

The example of the full app can be found at StackBlitz.

## Operator intervalBackoff

Have you ever wondered what your app is doing while you are sleeping? Among many tabs that are kept open is it still working hard querying your servers, using precious resources?

The second use case of exponential backoff is to reduce the frequency of the requests by exponentially increasing each delay between requests. This is handy technique may be applied when the app detects that there is no user activity — for example, no mouse movement.

Let’s take a look at the following piece of code.

``````import {fromEvent} from 'rxjs';
import {sampleTime, startWith, switchMap} from 'rxjs/operators';
import {intervalBackoff} from 'backoff-rxjs';
import {service} from './service';

const newData\$ = fromEvent(document, 'mousemove').pipe(

// There could be many mousemoves, we'd want to sample only
// with certain frequency
sampleTime(1000),

// Start immediately
startWith(null),

// Resetting exponential interval operator
switchMap(() => intervalBackoff(1000)),
switchMap(() => service.getData()),
);``````

Now let’s break it down what’s happening here:

• mousemove event is tracked on the `document` and use it as an indicator of user activity
• It is triggered very frequently when the mouse is moved, so we use `sampleTime` as a filter of those events
• `sampleTime` emits the first value only once the time specified expires. If we need to make the first call immediately (and in most cases we need it) then `startWith` helps us to do that
• now we are at the `intervalBackoff`, which is a pipeable operator that works similar to `interval`, however, instead of using the same delay between emissions it doubles the delay after each one
• Once `intervalBackoff` emits the value we do the service call

Note, that every time the mousemove event is detected it resets the `intervalBackoff`.

Here is the marble diagram for `intervalBackoff`:

Similarly to the `retryBackoff`, `intervalBackoff` is also configurable and can take more than just initial delay.

``````export interface IntervalBackoffConfig {
initialInterval: number;
maxInterval?: number;
backoffDelay?: (iteration: number, initialInterval: number) => number;
}``````

#### Demo

Example of the app using `intervalBackoff`:

### Summary

Exponential backoff is a very useful strategy and has at least two common use cases: interval backoff and retry backoff. `backoff-rxjs` package provides pipeable operators for both cases, and they are just a combination of existing RxJS operators.

Special thanks to Ben Lesh, Max Koretskiy and Nicholas Jamieson for reviewing the article/operators and providing valuable feedback.

Also to Valentin Hăloiu for the addition of `resetOnSuccess` configuration.

Share

#### Featured articles

RxJS
RxJS in Angular: Part III

In my previous two articles we have discussed how to change our components which solve problems in imperative ways to do that in functional, reactive, RxJS way, and we of course had a lot of fun doing that.

Angular