2

I've written a code in which I would like to catch an error when exception occurs:

this.assetApiService.getAssets(new AssetSearchCriteriaDto({tags: [AssetTags.tenant]})).pipe(
          catchError(error => {
           console.log(error);
          }),
          (mergeMap((assets: AssetDto[]) => {
                this.appInitStorageService.setAvailableTenants(assets);
                return this.userApiService.getUserById(this.authApiService.getAuth().userInfo.id);
              }
            )
          )
        )
          .subscribe((user: UserDto) => {
            this.persistSelectedUserLanguage(user);
            this.appInitStorageService.setUser(user);
            resolve();
          }, error => {
            console.log('error:', error);
          });

The goal is to catch errors either if they occur from first observable in sequence(getAssets) or in second observable(getUserById). I've added catchError operator in first one but I can't see console.log(error). I don't know why. How should I properly catch errors in this example?

Nicholas K
  • 15,148
  • 7
  • 31
  • 57
Krzysztof Michalski
  • 791
  • 1
  • 9
  • 25

2 Answers2

0

You could just move the catchError below the mergeMap to catch errors from both the observables. Also very important to remember that catchError must return an observable. You could use RxJS throwError function to throw an error observable

this.assetApiService.getAssets(new AssetSearchCriteriaDto({tags: [AssetTags.tenant]})).pipe(
  mergeMap((assets: AssetDto[]) => {
    this.appInitStorageService.setAvailableTenants(assets);
    return this.userApiService.getUserById(this.authApiService.getAuth().userInfo.id);
  }),
  catchError(error => {
    console.log(error);
    return throwError(error); // return error that will trigger the subscriptions `error` block
  })
).subscribe({
  next: (user: UserDto) => {
    this.persistSelectedUserLanguage(user);
    this.appInitStorageService.setUser(user);
    resolve();
  }, 
  error: error => {
    console.log('error:', error);
  }
});

If you on the other hand wish to convert errors to valid emissions and consequently keep the stream running instead of erroring out, you could use RxJS of function. The the errors will be emitted to the subscription's next callback instead of error callback

this.assetApiService.getAssets(new AssetSearchCriteriaDto({tags: [AssetTags.tenant]})).pipe(
  mergeMap((assets: AssetDto[]) => {
    this.appInitStorageService.setAvailableTenants(assets);
    return this.userApiService.getUserById(this.authApiService.getAuth().userInfo.id);
  }),
  catchError(error => {
    console.log(error);
    return of(error); // return error that will trigger the subscriptions `next` block
  })
).subscribe({...});
ruth
  • 29,535
  • 4
  • 30
  • 57
  • I've changed order of operators but I still can't see console log. Don't you know why? – Krzysztof Michalski Nov 16 '20 at 11:45
  • Do you mean the `console.log` inside `catchError` or the subscription? – ruth Nov 16 '20 at 11:52
  • Yes. I can't see it. – Krzysztof Michalski Nov 16 '20 at 11:54
  • That isn't a yes/no question? Which `console.log` do you mean? – ruth Nov 16 '20 at 11:55
  • I mean this part: catchError(error => { console.log("error", error); return of(error); // return error that will trigger the subscriptions `next` block }) – Krzysztof Michalski Nov 16 '20 at 12:15
  • In that case the observables isn't emitting an error. `catchError` will only be triggered when the observable emits an error. Note: observable error is not the same as an HTTP error. An HTTP request may emit an error message as a valid notification in the observable. – ruth Nov 16 '20 at 12:36
  • Ok. Should I add catching error inside http Observable? I can see also there pipe method: return this.sharedService.httpGetWithPagination( this.sharedService.buildApiUrl('assets'), this.assetMapperService.assetSearchCriteriaDtoToAssetSearchCriteria(assetSearchCriteriaDto), withFurtherPages, ).pipe( map((assets: Asset[]) => this.assetMapperService.assetArrayToAssetDtoArray(assets)), reduce((all: AssetDto[], current: AssetDto[]) => all.concat(current)), ); – Krzysztof Michalski Nov 16 '20 at 12:42
  • No there is no difference in adding `catchError` in the `mergerMap` or the HTTP request. You need to make sure the HTTP request in itself throws an error instead of a valid response. Or if you wish to do it instead in the frontend, you need to catch the response using `switchMap` and throw error using `throwError` function. This is inelegant, but will work. – ruth Nov 16 '20 at 12:47
  • Yes I know. I received 401 error in browser. I've intentionally changed value of JWT token in order to receive an error. – Krzysztof Michalski Nov 16 '20 at 13:45
0

Is your code compiling? You have to return an Observable in catchError:

catchError(error => {
    console.log(error);
    return of([ ]);
}),

This way the stream can continue processing after the error was caught. As you see catchError only makes sense if you can restore the control flow - otherwise the error can bubble up to the subscribe error handler. A big difference is here that the observable stops emitting if an error reaches subscribe, while with catchError it will continue emitting (this is not relevant in your case but would be e.g. in case of an interval or fromEvent Observable that emits multiple values).

If that doesn't solve your issue, make sure that this.assetApiService.getAssets actually throws an exception. Maybe it just returns a response with an error response code for some reason?

code-gorilla
  • 2,231
  • 1
  • 6
  • 21
  • _"`catchError` only makes sense if you can restore the control flow"_ - that is debatable. In a stream with multiple observables, I could handle errors from each observable individually. But that doesn't mean the flow has to be restored. I could return the error out with `throwError` that could be caught by the more generic error handling in the subscription. Simply put, it depends on the use-case. In some cases the stream needs to be stopped after handling the error and in some cases it doesn't necessarily need to be. – ruth Nov 16 '20 at 11:02
  • One other common use case is error handling + `retry`/`retryWhen`. The `catchError` is used to handle the error and emitting the error again using `throwError` ensures the stream is *restarted* using `retry` or `retryWhen` operator. – ruth Nov 16 '20 at 11:06