LiveLoveApp logo

Retry Operator

retry() Operator

The rety() operator accepts an optional number of retries, and when the source Observable emits an error, the operator returns a clone of the source Observable excluding the error notification. As you can imagine, internally, the retry() operator tracks the number of retries, and when reached, will return the source Observable with the error notification.

A word of caution, the retry operator works with both hot and cold Observables, but may lead to some confusion when using a hot observable. This is because the "retry" will not occur until the hot source Observable emits a next notification.

Example

Let's take a look an example of using the retry() operator. In this example we'll retry a failed Observable twice:

import { Subject, throwError, defer } from 'rxjs';
import { catchError, finalize, retry, tap } from 'rxjs/operators';

const subject = new Subject<void>();

defer(() => {
  console.log('defer');
  return subject;
})
  .pipe(
    tap(() => {
      throw new Error('Error emitted by throw');
    }),
    catchError((error) => {
      console.error('catchError', error);
      return throwError(error);
    }),
    retry(2),
    finalize(() => console.log('finalize'))
  )
  .subscribe({
    error: (e) => console.error('observer', e),
    next: (value) => console.log('next', value),
    complete: () => console.log('complete')
  });

subject.next();
subject.next();
subject.next();

See example on codesandbox

Let's break this down:

  • First, we create a new Subject whose generic type is void.
  • Then, we'll use the defer() operator in order to perform a side effect upon subscribing. The defer() operator accepts a function that is invoked every time an Observable is subscribed to, and returns the Observable. In this example, we'll log the "defer" message to the console and return the subject.
  • Immediately in the tap() operator we throw a new Error.
  • The catchError() operator's source Observable has emitted an error. So, the catchError() selector function is invoked with the error value provided as the first argument. Within the selector function we'll use the console.error() method to log the error to the console, and then we return the Observable returned from the throwError() operator, providing the error value.
  • Now we use the retry() operator and specify 2 for the count. This means that the retry operator will retry the source Observable that emits an error notification two (additional) times.
  • For some additional fun, we'll also use the finalize() operator to log out the message finalize() when the source Observable has emitted an error notification, which will be a result of the two retries.
  • Then, we subscribe to the Observable and provide an Observer.
  • Finally, note that we are emitting three next notifications on the subject instance.

What should the logging look like in the console?

Here is what the console should log:

  1. defer
  2. catchError Error: Error emitted by throw
  3. defer
  4. catchError Error: Error emitted by throw
  5. defer
  6. catchError Error: Error emitted by throw
  7. observer Error: Error emitted by throw
  8. finalize

This may be a bit confusing, so let's explain each log to the console in sequence:

  1. The Subject is subscribed to via the subscribe() method, and remember that we are logging "defer" each time the source Observable is subscribed to.
  2. The catchError() operator catches the first error when we emit the first next notification and the tap() operator throws an error.
  3. The retry() operator receives the Observable from the throwError() operator that immediately emits an error notification. As such, we'll retry the first time. Internally, the count is decremented to 1. The retry() operator subscribes to a mirror of the source Observable without the error notification. Therefore, the "defer" message is logged to the console.
  4. Round we go. The catchError() operator receives the error notification again from the previous tap() operator.
  5. We retry for the second time. Internally, the count is decremented to 0. Therefore, the "defer" message is logged to the console.
  6. Round we go. The catchError() operator receives the error notification again from the previous tap() operator. Internally, the count is decremented to -1. As such, we're done retrying.
  7. With the error being rethrown and the retries extinguished, the error() function on the Observer is invoked with the error value.
  8. Finally, the finalize() operator invokes the callback method and logs the message "finalize" to the console.