ReTry & ReTryWhen Operators help us to retry a failed observable in Angular. These operators are useful in Error handling. They both resubscribe to the source observable when they receive onError()
notification.
Table of Contents
ReTry
The ReTry Angule RxJs operator retries a failed source observable count
number of times. If the count is not provided then it tries indefinitely
Syntax
1 2 3 | retry<T>(count: number = -1): MonoTypeOperatorFunction<T> |
Mirrors the source observable, if there are no errors.
If the source observable calls error notification, it does not propagate it. But re subscribes to the source again for a maximum of count
times.
The Retry operator retries the operation immediately
ReTry Example
In the example below, map operator throws an error if the source emits a value greater than 3.
The ReTry(2) retries the operation twice before erroring out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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") ); } } |
Retry without any argument, will retry indefinitely
1 2 3 4 5 6 7 8 9 10 11 | interval(1000) .pipe( map(val => { if (val > 2) throw new Error("Invalid Value"); return val; }), retry() ) .subscribe(val => console.log(val)); |
Retry(0) never retries.
1 2 3 4 5 6 7 8 9 10 11 | interval(1000) .pipe( map(val => { if (val > 2) throw new Error("Invalid Value"); return val; }), retry(0) ) .subscribe(val => console.log(val)); |
ReTryWhen
The RetryWhen Angule RxJs operator retries the failed Observable every time a Notification Observable emits the next value.
Syntax
1 2 3 | retryWhen<T>(notifier: (errors: Observable<any>) => Observable<any>): MonoTypeOperatorFunction<T> |
Where notifier
is the callback, which returns the Notification Observable
How it works
We register the notifier
callback with the ReTryWhen Operator.
The notifier
gets the errors
observable as the argument, which emits whenever the source observable errors
We return the notifier
observable, which we build using the errors
observable.
The ReTryWhen subscribes to this notifier
observable and how it behaves depends on the value emitted by the notifier
observable
- If the notifier emits a value, then ReTryWhen re subscribes the source observable.
- In case of notifier emitting an error, then ReTryWhen also emits an error.
- If the notifier completes, then ReTryWhen does nothing.
ReTryWhen Example
In the following example, map operator throws an error if the val > 2.
The errors are caught by retryWhen
. It gets the error
observable as its input. We use the pipe operator to add a tap to print the Retrying
message on the console.
This code runs indefinitely as the source will always errors out and there is nothing to stop retryWhen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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 observable as it is also retries the source indefinitely
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 delay of 2000 ms.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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
As long as the notifier observable emits a value, the retryWhen will re subscribes to the source again.
In the following example, we switch observable to a new observable using SwitchMap operator. The new observable (of Operator) emits the value 1 after a delay of 1000 ms and the source is resubscribed again.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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") ); |
The following uses of()
instead of(1)
. of()
does not emit any values but sends a complete notification on subscription. In this case, RetryWhen does not retry the source but completes. The subscribers will not receive any notification.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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") ); |
Here is another interesting example where we switch to interval(3000)
observable.
When the error occurs the first time in the source, RetryWhen triggers the interval(3000). When it emits a value source is re-subscribed.
But, the interval(3000)
is going to emit another value after 3000ms. In this case, the RetryWhen will re subscribes to the source again, even if the source has errored out or already completed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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
In the example below, we use the scan operator to count the number of tries and throw an error if the number of attempts exceeds 2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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 |
Use the dealyWhen to increase the time duration between each retries.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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") ); |
You can also use the take
or takeWhile operator to stop the retries and emit the complete notification.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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") ); } |
References
Read More
RetyWhen is now deprecated and we should use Retry instead it: https://rxjs.dev/api/operators/retryWhen