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 thenotifier
Observable. Thenotifier
Observable emits a next notification each time the source Observable emits an error notification. - We use the
mergeMap()
operator on thenotifier
Observable in order to merge the result of the inner Observables, which will either be an Observable created by theof()
operator that will immediately emit anull
next notification, or the Observable created by thethrowError()
operator that will immediately emit an error notification. - Within the
mergeMap()
we set aMAX_RETRIES
constant to the number2
. This will prevent endless retries. ThemergeMap()
operator's projection function is invoked with the value emitted from the sourcenotifier
Observable 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
i
in our example, is less thanMAX_RETRIES
we check the error'sstatus
code 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.