The retry operator works by subscribing to the caught observable in case of an error until the attempts are exhausted.
Let’s break down the above and translate it to human language.
For that we’ll do the following:
retry
operatorretry
operatorFor a quick refresher on Observable - it has a subscribe
method that accepts 3 callbacks:
next
- called in case of emissionerror
- called in case of an errorThis next code snippet demonstrates an Observable that emits once and then completes:
const { of } = require('rxjs');const f = of('emission');f.subscribe(x => console.log(x),(e, observable) => { /** error case */},() => { console.log('complete')})
const { of } = require('rxjs');const f = of('emission');const subscriber = {next: x => console.log(x),error: (e, observable) => { /** error case */},complete: () => { console.log('complete')}}f.subscribe(subscriber);
The above demonstrates how a subscriber object can be used in the subscribe method and it will work the same.
retry
basicsThe retry operator will retry the observable in case of an error.
Take a look at the code below for an example:
const { of, throwError } = require('rxjs');const { retry, switchMap } = require('rxjs/operators');let calls = 0;const throwsErrorFirstEmission = of('emission').pipe(switchMap(e => {if(calls === 0) {calls += 1;console.log('throwing error!')return throwError('e');}return of(e);}));throwsErrorFirstEmission.pipe(retry(1)).subscribe(x => console.log(x),(e, observable) => { console.log(e) },() => { console.log('complete')})
So, what happens here?
We are declaring in our pipe
method that we’d like to retry
this observable chain 1 time, making the total maximum attempts 2. That’s 1 initially and 1 retry.
The retry
operator will subscribe to the observable (at subscribe-time - line 20) and will listen for the error
case. In case of error, it will re-attempt the observable unless the number of attempts is equal to the provided value.
If we were to unwrap the retry
operator in code, it would look like:
const { of, throwError } = require('rxjs');const { switchMap } = require('rxjs/operators');let calls = 0;const throwsErrorFirstEmission = of('emission').pipe(switchMap(e => {if (calls === 0) {calls += 1;console.log('throwing error!')return throwError('e');}return of(e);}));// want to reuse the subscriber but decrease the attemptsfunction subscriber(upTo) {return {next: x => console.log(x),error: retryUpTo(upTo),complete: () => { console.log('complete') }}};const retryUpTo = (upTo) => {return (error) => {// if there are attempts left - retry!if (upTo > 0) {throwsErrorFirstEmission.subscribe(subscriber(upTo - 1));} else {throw console.error(e);}}}throwsErrorFirstEmission.subscribe(subscriber(1))
Sorry about the convoluted code, let’s try to break it down:
The highlighted function will get called if the observable chain fails. It will then proceed to do the following:
That’s what the retry
operator does!
What happens if the attempts are over but the observable still throws?
Then the retry will pass on the error.
For simplicity, we just use console.error
here:
const { of, throwError } = require('rxjs');const { switchMap } = require('rxjs/operators');let calls = 0;const throwsErrorFirstEmission = of('emission').pipe(switchMap(e => {if (calls === 0 || calls === 1) {calls += 1;console.log('throwing error!')return throwError('e');}return of(e);}));// want to reuse the subscriber but decrease the attemptsfunction subscriber(upTo) {return {next: x => console.log(x),error: retryUpTo(upTo),complete: () => { console.log('complete') }}};const retryUpTo = (upTo) => {return (error) => {// if there are attempts left - retry!if (upTo > 0) {throwsErrorFirstEmission.subscribe(subscriber(upTo - 1));} else {console.error(error);}}}throwsErrorFirstEmission.subscribe(subscriber(1))
If we switch to using the actual retry
operator, the behavior is the same:
const { of, throwError } = require('rxjs');const { retry, switchMap } = require('rxjs/operators');let calls = 0;let allowed = 2;const throwsErrorTwice = of('emission').pipe(switchMap(e => {if(calls < allowed) {calls += 1;console.log('throwing error!')return throwError('e');}return of(e);}));throwsErrorTwice.pipe(retry(1)).subscribe(x => console.log(x),(e) => console.error(e),() => { console.log('complete')})
We implemented logic to do the following:
We then showed that switching to the actual retry
operator retains the behavior of that logic.
The
retry
operator works by subscribing to the caught observable, in case of an error, until the attempts are exhausted.
Hopefully, that makes more sense now.
If not go back and read it again :)