RXJS catch outer observable error and remaining sequence

Viewed 503

Came across an rxjs issue I can't seem to quite get. basically I have two requests:

const obs1$ = this.http.get('route-1')
const obs2$ = this.http.get('route-2')

if obs1$ emits an error, i want to catch it and just emit a static value. but if obs1$ completes, I want to switch into obs2$ and not catch errors from obs2$. I have it working like this:

obs1$.pipe(
   catchError(() => of('my value')),
   switchMap((v) => v === 'my value' ? of(v) : obs2$)
).subscribe(
   (v) => console.log(v, 'got my result'),
   (e) => console.log(e, 'got an error')
)

but this seems a little messy and I'm wondering if there's a better way to achieve this. I can't move the catchError after the switchMap cause then i'll be catching errors from obs2$ as well which I do not want. I just want to skip to the end if i get an error from obs1$

2 Answers

You could move the catchError below the switchMap but in addition use another catchError for obs2$ to not emit anything in case of it's error.

obs1$.pipe(
  switchMap(() => obs2$.pipe(
    catchError(() => NEVER)       // or `of('someValue')` if you wish  
  )),
  catchError(() => of('my value')),
).subscribe(
   (v) => console.log(v, 'got my result'),
   (e) => console.log(e, 'got an error')
)

I think this would work:

obs1$.pipe(
  // the happy path: `obs1$` completed successfully
  concatWith(obs2$),
  catchError(() => of("static value"))
)

concatWith is defined as:

// `source` in this cases refers to `obs1$`
// `...args` is ...[obs2$]
concatAll()(internalFromArray([source, ...args],  )).subscribe(subscriber as any);

So, the only way that obs2$ is subscribed is only if obs$1 completes.

concatAll()(internalFromArray([source, ...args], )) is a terse way of (roughly) writing:

from([source, ...args]).pipe(
  concatAll(),
)

Now, concatAll is defined as return mergeMap(identity, concurrent);.

The interesting thing about mergeAll is that when an inner observable emits an error, the error notification will be passed along to the destination, in this case it will be catchError.


Quick fix

I forgot about the requirement which says that errors coming from obs2$ should not be caught:

obs1$.pipe(
  concatWith(obs2$),

  // `caught$` refers to the observable which the errors comes from
  catchError((err, caught$) => caught$ === obs1$ ? of("static value") : throwError(err));
)
Related