Reputation: 3014
When I make a GET request using the Angular HttpClient, I get an observable back and process it within the RxJS operator mergeMap.
Now it happens that again and again a 404 is thrown, which I would like to catch. Finally, no error message should appear in the browser console and the pipe should be processed with the next value of the stream.
Is there a possibility for that? I did not manage it with catchError().
Here is a simplified version of my code:
...
this.service1.getSomeStuff().pipe(
mergeMap((someStuff) => {
return from(stuff);
}),
mergeMap((stuff) => {
return this.service2.getMoreStuff(stuff.id); // Here I need some error handling, if 404 occurs
}),
mergeMap((things) => {
return from(things).pipe(
mergeMap((thing) => {
if (allLocations.some(x => x.id === metaData.id)) {
return this.service2.getMore(thing.id, thing.type, thing.img_ref);
}
}),
map((thing) => {
...
UPDATE: Added approach with catchError()
I tried it that way, but the error is not detected and the next mergeMap does not work (IDE does not recognize parameter like thing.id, thing.type, thing.img_ref anymore):
...
this.service1.getSomeStuff().pipe(
mergeMap((someStuff) => {
return from(stuff);
}),
mergeMap((stuff) => {
return this.service2.getMoreStuff(stuff.id).pipe(
catchError(val => of(`Error`))
);
}),
mergeMap((things) => {
return from(things).pipe(
mergeMap((thing) => {
if (allLocations.some(x => x.id === metaData.id)) {
return this.service2.getMore(thing.id, thing.type, thing.img_ref);
}
}),
map((thing) => {
...
Upvotes: 5
Views: 14849
Reputation: 5364
You'll need to use retry
or retryWhen
(names are pretty self-explanatory) — these operators will retry a failed subscription (resubscribe to the source observable, once an error is emitted.
To raise the id
upon each retry — you could lock it in a scope, like this:
const { throwError, of, timer } = rxjs;
const { tap, retry, switchMap } = rxjs.operators;
console.log('starting...');
getDetails(0)
.subscribe(console.log);
function getDetails(id){
// retries will restart here
return of('').pipe(
switchMap(() => mockHttpGet(id).pipe(
// upon error occurence -- raise the id
tap({ error(err){
id++;
console.log(err);
}})
)),
retry(5) // just limiting the number of retries
// you could go limitless with `retry()`
)
}
function mockHttpGet(id){
return timer(500).pipe(
switchMap(()=>
id >= 3
? of('success: ' + id)
: throwError('failed for ' + id)
)
);
}
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>
Please, note that it would be wiser to have a conditional retry
to retry only on 404
errors. That could be achieved via retryWhen
, e.g.
// pseudocode
retryWhen(errors$ => errors$.pipe(filter(err => err.status === '404')))
Check this article on error handling in rxjs to get more affluent with retry
and retryWhen
.
Hope this helps
UPDATE: there are also other ways to achieve that:
const { throwError, of, timer, EMPTY } = rxjs;
const { switchMap, concatMap, map, catchError, take } = rxjs.operators;
console.log('starting...');
getDetails(0)
.subscribe(console.log);
function getDetails(id){
// make an infinite stream of retries
return timer(0, 0).pipe(
map(x => x + id),
concatMap(newId => mockHttpGet(newId).pipe(
// upon error occurence -- suppress it
catchError(err => {
console.log(err);
// TODO: ensure its 404
// we return EMPTY, to continue
// with the next timer tick
return EMPTY;
})
)),
// we'll be fine with first passed success
take(1)
)
}
function mockHttpGet(id){
return timer(500).pipe(
switchMap(()=>
id >= 3
? of('success: ' + id)
: throwError('failed for ' + id)
)
);
}
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>
Upvotes: 5