ReTry, ReTryWhen In Angular

ReTry, ReTryWhen In Angular

The ReTry and ReTryWhen operators in Angular allow us to retry a failed observable. These operators can particularly handy when dealing with errors. When they receive an onError() message, they both resubscribe to the source observable.

ReTry

The RxJs operation ReTry Angule retries a failed source observable a specified number of times. It attempts endlessly if the count is not specified.

Syntax

retry<T>(count: number = -1): MonoTypeOperatorFunction<T>

If there are no errors, it mirrors the source observable.

Error notice is not propagated if the source observable calls it. However, for a maximum of count times, resubscribes to the source.

The Retry operator quickly retries the operation.

ReTry Example

If the source emits a value larger than 3, the map operator throws an error in the example below.

Before failing, the ReTry(2) method tries the operation twice more.

import { Component } from "@angular/core";
import { map, retry, tap } from "rxjs/operators";
import { interval } from "rxjs";
 
@Component({
  selector: "my-app",
  template: `
    <h1>Retry And ReTryWhen Example</h1>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  ngOnInit() {
    interval(1000)
      .pipe(
        map(val => {
          if (val > 2) throw new Error("Invalid Value");
          return val;
        }),
        retry(2)
      )
      .subscribe(
        val => console.log(val),
        err => console.log(err),
        () => console.log("Complete")
      );
  }
}

Retrying without any arguments will result in an endless number of retries.

interval(1000)
  .pipe(
    map(val => {
      if (val > 2) throw new Error("Invalid Value");
      return val;
    }),
    retry()
  )
  .subscribe(val => console.log(val));

Retry(0) does not retry.

interval(1000)
  .pipe(
    map(val => {
      if (val > 2) throw new Error("Invalid Value");
      return val;
    }),
    retry(0)
  )
  .subscribe(val => console.log(val));

ReTryWhen

The Second Chance When a Notification Observable emits the next value, the Angular RxJs operator retries the failed Observable.

Syntax

retryWhen<T>(notifier: (errors: Observable<any>) => Observable<any>): MonoTypeOperatorFunction<T>

The callback that returns the Notification Observable is called a notifier.

How it works

The ReTryWhen Operator is used to register the notifier callback.

The source observable errors are passed to the notifier as a parameter, and the notifier emits whenever the source observable errors.

We return the notifier observable, which is constructed from the errors observable.

This notifier observable is subscribed to by the ReTryWhen, and how it behaves is determined by the value emitted by the notifier observable.
  • ReTryWhen resubscribes the source observable if the notifier emits a value.
  • If the notifier returns an error, ReTryWhen returns an error as well.
  • ReTryWhen does nothing if the notifier completes.

ReTryWhen Example

If the val > 2 in the following example, the map operator throws an error.

retryWhen is used to catch the errors. As input, it receives the error observable. To print the Retrying message on the console, we use the pipe operator to add a tap.

This code will continue to execute eternally because the source will always fail and there is nothing to stop it retryWhen.

import { Component } from "@angular/core";
import { map, retryWhen, tap, take } from "rxjs/operators";
import { interval, Observable, observable, of, throwError, timer } from "rxjs";
 
@Component({
  selector: "my-app",
  template: `
    <h1>Retry And ReTryWhen Example</h1>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  ngOnInit() {
    interval(1000)
      .pipe(
       map(val => {
          if (val > 2) throw new Error("Invalid Value");
          return val;
        }),
       retryWhen(
           error => error.pipe(
              tap(() => console.log("Retrying... "))))
       ) 
      .subscribe(
        val => console.log(val),
        err => console.log(err),
        () => console.log("Complete")
      );
  }
}
 
**Console **
 
 
0
1
2
Retrying..
0
1
2
Retrying..

Returning the error as it also causes the source to be retried endlessly.

interval(1000)
    .pipe(
      map(val => {
        if (val > 2) throw new Error("Invalid Value");
        return val;
      }),
      retryWhen(error => error)
    )
    .subscribe(
      val => console.log(val),
      err => console.log(err),
      () => console.log("Complete")
    );

Adding a Delay

The following code adds a 2000 millisecond delay.

interval(1000)
  .pipe(
    map(val => {
      if (val > 2) throw new Error("Invalid Value");
      return val;
    }),
    retryWhen(
      error =>
        error.pipe(
          tap(() => console.log("error occurred ")),
          delay(2000),
          tap(() => console.log("Retrying ..."))
        )
    )
  )
  .subscribe(
    val => console.log(val),
    err => console.log(err),
    () => console.log("Complete")
  );

Notifier observable

The retryWhen will resubscribe to the source as long as the notifier observable emits a value.

The SwitchMap operator is used in the following example to switch an observable to a new observable. After a 1000 ms delay, the new observable (of Operator) emits the value 1, and the source is resubscribed.

interval(1000)
 .pipe(
   map(val => {
     if (val > 2) throw new Error("Invalid Value");
     return val;
   }),
   retryWhen(error =>
     error.pipe(
       switchMap(() =>
         of(1).pipe(
           delay(1000),
           tap(() => console.log("of emitted"))
         )
       ),
       tap(() => console.log("Retrying ..."))
     )
   )
 )
 .subscribe(
   val => console.log(val),
   err => console.log(err),
   () => console.log("Complete")
 );

Instead of using of(), the following uses of() (1). of() does not return any values but instead transmits a complete subscription notification. RetryWhen completes rather than retrying the source in this situation. There will be no notification sent to the subscribers.

interval(1000)
  .pipe(
    map(val => {
      if (val > 2) throw new Error("Error: Value greater than 2");
      return val;
    }),
    retryWhen(error =>
      error.pipe(
        switchMap(() =>
          of().pipe(
            delay(1000),
            tap(() => console.log("of emitted"))
          )
        ),
        tap(() => console.log("Retrying ..."))
      )
    )
  )
  .subscribe(
    val => console.log(val),
    err => console.error(err),
    () => console.log("Complete")
  );

Another interesting example is when we use interval(3000) as the observable.

RetryWhen starts the interval when the error occurs for the first time in the source (3000). A valued source is re-subscribed when it emits.

However, after 3000ms, the interval(3000) will emit a new value. In this example, the RetryWhen will resubscribe to the source, even if it has already been completed or has failed.

interval(1000)
  .pipe(
    map(val => {
      if (val > 2) throw Error;
      return val;
    }),
    retryWhen(error =>
      error.pipe(
        tap(() => console.log("error tapped")),
        switchMap(() =>
          interval(3000).pipe(tap(() => console.log("interval")))
        ),
        tap(() => console.log("Retrying ..."))
      )
    )
  )
  .subscribe(
    val => console.log(val),
    err => console.log(err),
    () => console.log("Complete")
  );
 
*** Console ***
0
1
2
error tapped
interval
Retrying ...
0
1
interval
Retrying ...
0
1
interval
Retrying ...

Limiting Retry Attempts

We use the scan operator to count the number of tries in the example below, and we throw an exception if the number of tries exceeds 2.

interval(1000)
  .pipe(
    map(val => {
      if (val > 2) throw new Error("Invalid Value");
      return val;
    }),
    retryWhen(error =>
      error.pipe(
        scan((acc, error) => {
          if (acc > 2) throw error;
          console.log("attempt " + acc);
          return acc + 1;
        }, 1),
        delay(3000),
        tap(() => console.log("Retrying ..."))
      )
    )
  )
  .subscribe(
    val => console.log(val),
    err => console.error(err),
    () => console.log("Complete")
  );
 
 
*** Console ***
0
1
2
attempt 1
Retrying ...
0
1
2
attempt 2
Retrying ...
0
1
2
Invalid Value

To increase the time between retries, use the dealyWhen command.

interval(1000)
  .pipe(
    map(val => {
      if (val > 2) throw new Error("Invalid Value");
      return val;
    }),
    retryWhen(error =>
      error.pipe(
        scan((acc, error) => {
          if (acc > 2) throw error;
          console.log("attempt " + acc);
          return acc + 1;
        }, 1),
        delayWhen(val => timer(val * 2000)),
        tap(() => console.log("Retrying ..."))
      )
    )
  )
  .subscribe(
    val => console.log(val),
    err => console.error(err),
    () => console.log("Complete")
  );

To stop the retries and emit the whole notification, use the take or takeWhile operators.

  interval(1000)
    .pipe(
      map(val => {
        if (val > 2) throw new Error("Invalid Value");
        return val;
      }),
      retryWhen(error =>
        error.pipe(
          scan((acc, error) => {
            console.log("attempt " + acc);
            return acc + 1;
          }, 1),
          take(2),
          delayWhen(val => timer(val * 2000)),
          tap(() => console.log("Retrying ..."))
        )
      )
    )
    .subscribe(
      val => console.log(val),
      err => console.error(err),
      () => console.log("Complete")
    );
}

Conclusion

In this article, we learned how to use ReTry, and ReTryWhen in angular and how to use it.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in the comment section.

Post a Comment

Previous Post Next Post