The RxJS retry operator mirrors the source Observable except for the error. This allows to retry an operation after an error happens. The operator accepts a retry count as a number or a more complex RetryConfig
.
retry<T>(configOrCount: number | RetryConfig = Infinity): MonoTypeOperatorFunction<T>
This config allows for example to set a delay for the retry or retry only if a notifier Observable emits.
RxJS retry with a count
As mentioned above, a count can be passed to the retry operator to specify the number of retries. This is probably the simplest variant of the operator. In the below code, we are retrying twice on a source Observable. But no luck: it returns an error every time. After the second retry, we are forwarding the error to the subscribers:
const source$ = cold('abc#def').pipe(
retry(2)
);
expectObservable(source$).toBe(
'abcabcabc#'
);
RxJS retry with changed outcome
We should only retry if there is also a chance to get a different outcome than the first time we try. We know that distributed systems are not 100% reliable. Every time we call an HTTP service something could go wrong. Therefore a retry makes sense in that area because on a second try, the response could be different.
In the below code, a request is executed that first returns 503 as a response. Thanks to retry the request happens again and on the second try, it is a success.
let i = 0;
const result$ = of('request').pipe(
mergeMap(_ => ++i === 1 ? throwError(() => '503') : of('200')),
retry(1),
);
expectObservable(result$).toBe(
'(r|)', { r: '200' }
);
To the consumer or observer, the retry is not really visible. It is part of the stream.
Only retry after a certain delay
The RetryConfig of the RxJS retry operator allows us to specify a retry delay in milliseconds or a notifier Observable that causes a retry once it emits. Here we are going for the notifier Observable that triggers after a certain time:
let i = 0;
const result$ = of('request').pipe(
mergeMap(_ => ++i === 1 ? throwError(() => '503') : of('200')),
retry({ count: 1, delay: () => cold('--t')}),
);
expectObservable(result$).toBe(
'--(r|)', { r: '200' }
);
Also here the response is eventually a success. But we have waited a bit to give the async operation time to recover.
Exercise for the RxJS retry operator 💪
Use a retry config to retry twice, but wait a bit longer on the 2nd retry if the error is an error 503. You can use the below setup as a starting point:
const result$ = of('request').pipe(
mergeMap(_ => ++i < 3 ? throwError(() => '503') : of('200')),
retry({ count: 2, delay: (error, retryCount) => {
// your code
return cold('--t');
}}),
);
expectObservable(result$).toBe(
'-----(r|)', { r: '200' }
);
This post is part of the RxJS mastery series. As always the code examples can be found on GitHub.