Solution - RetryWhen Operator
Solution
import { fromEvent, of, throwError } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import {
catchError,
finalize,
map,
mergeMap,
retryWhen
} from 'rxjs/operators';
interface UserResponse {
data: {
id: number;
email: string;
first_name: string;
last_name: string;
avatar: string;
};
}
function random(max: number, min: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const output = document.getElementById('output') as HTMLTextAreaElement;
const btn = document.getElementById('btn') as HTMLButtonElement;
fromEvent(btn, 'click')
.pipe(
map(() => random(10, 15)),
mergeMap((id) =>
ajax.getJSON<UserResponse>(`https://reqres.in/api/users/${id}`).pipe(
map((response) => response.data),
catchError((error) => {
output.value += `\n\n${error.message}`;
output.scrollTop = output.scrollHeight;
return throwError(error);
}),
retryWhen((notifier) =>
notifier.pipe(
mergeMap((error, i) => {
// retry maximum of 2 times when the status code is 404
const MAX_RETRIES = 2;
if (i < MAX_RETRIES) {
if (error.status === 404) {
return of(null);
}
}
return throwError(error);
})
)
)
)
),
finalize(() => {
btn.classList.add('cursor-not-allowed');
btn.classList.add('opacity-50');
})
)
.subscribe({
error: (e) => console.error('observer', e),
next: (value) => {
output.value += `\n\n${JSON.stringify(value, null, 2)}`;
output.scrollTop = output.scrollHeight;
},
complete: () => console.log('complete')
});
Let's focus on the retryWhen() operator in the code solution above.
- The
retryWhen()operator is going to retry the source Observable, which in this case, is the Observable created by theajax.getJSON()method. - When the source Observable emits an error notification, the
retryWhen()will invoke the callback function and provider thenotifierObservable. ThenotifierObservable emits a next notification each time the source Observable emits an error notification. - We use the
mergeMap()operator on thenotifierObservable in order to merge the result of the inner Observables, which will either be an Observable created by theof()operator that will immediately emit anullnext notification, or the Observable created by thethrowError()operator that will immediately emit an error notification. - Within the
mergeMap()we set aMAX_RETRIESconstant to the number2. This will prevent endless retries. ThemergeMap()operator's projection function is invoked with the value emitted from the sourcenotifierObservable and the index. The index is a count of the number of next notification the source Observable has emitted. We can use the index argument to track how many times Observable created by theajax.getJSON()has emitted an error notification. - If the index, which is
iin our example, is less thanMAX_RETRIESwe check the error'sstatuscode and return an Observable using theof()operator, which immediately emits the next notification value ofnull. - Else, we return a new Observable created by the
throwError()Observable that immediately emits an error notification. TheretryWhen()operator subsequently invokes theerror()method on the source Observable.