137

I want to throw an error from my observable's map operator based on a condition. For instance if correct API data is not received. Please see the following code:

private userAuthenticate( email: string, password: string ) {
    return this.httpPost(`${this.baseApiUrl}/auth?format=json&provider=login`, {userName: email, password: password})
        .map( res => { 
            if ( res.bearerToken ) {
                return this.saveJwt(res.bearerToken); 
            } else {
                // THIS DOESN'T THROW ERROR --------------------
                return Observable.throw('Valid token not returned');
            }
        })
        .catch( err => Observable.throw(this.logError(err) )
        .finally( () => console.log("Authentication done.") );
}

Basically as you can see in the code, if the response (res object) doesn't have bearerToken I want to throw out an error. So that in my subscription it goes into the 2nd parameter (handleError) mentioned below.

.subscribe(success, handleError)

Any suggestions?

MackM
  • 2,906
  • 5
  • 31
  • 45
Hassan
  • 2,308
  • 5
  • 21
  • 31

4 Answers4

191

Just throw the error inside the map() operator. All callbacks in RxJS are wrapped with try-catch blocks so it'll be caught and then sent as an error notification.

This means you don't return anything and just throw the error:

map(res => { 
  if (res.bearerToken) {
    return this.saveJwt(res.bearerToken); 
  } else {
    throw new Error('Valid token not returned');
  }
})

The throwError() (former Observable.throw() in RxJS 5) is an Observable that just sends an error notification but map() doesn't care what you return. Even if you return an Observable from map() it'll be passed as next notification.

Last thing, you probably don't need to use .catchError() (former catch() in RxJS 5). If you need to perform any side effects when an error happens it's better to use tap(null, err => console.log(err)) (former do() in RxJS 5) for example.

Jan 2019: Updated for RxJS 6

martin
  • 93,354
  • 25
  • 191
  • 226
  • 1
    Thanks @martin - Yes your solution works. I actually had a problem inside my logError method as well which @GünterZöchbauer pointed out. I had to `return` the error object from it and now it works perfectly :) Thanks! – Hassan Apr 04 '17 at 06:42
  • @martin: Could you please develop why we wouldn't want to you .catch() here? – Bob Jul 06 '17 at 07:18
  • 1
    @Bob Because the OP was using `catch()` only to log and rethrow the error, which unnecessary if you just want to perform a side-effect (logging the error) and it's easier to use just `do()` – martin Jul 06 '17 at 09:17
  • 1
    Is this identical to `return throwError(new Error('Valid token not returned'));` ? – Simon_Weaver Jul 09 '18 at 23:50
  • @Simon_Weaver no it's not. `return throwError()` returns an `Observable`, this just interrupts the observable stream immediately, without returning at all. – RaidenF Dec 27 '18 at 13:49
  • Documentation for the `catch` to `catchError` change and the `throw` to `throwError` change can be found [here](https://rxjs-dev.firebaseapp.com/guide/v6/migration) – Stubbs Apr 26 '19 at 19:37
  • one could use `throw new Error('Error');` and use `catchError(err => of(console.log('message:', err))),` [throwError doc](https://www.learnrxjs.io/operators/error_handling/catch.html) `:warning: Remember to return an observable from the catchError function!` – Ravi Anand Nov 28 '19 at 09:47
51

If you feel like throw new Error() seems un-observable-like you can use return throwError(...) with switchMap instead of map (the difference being switchMap has to return a new observable instead of a raw value):

// this is the import needed for throwError()
import { throwError } from 'rxjs';


// RxJS 6+ syntax
this.httpPost.pipe(switchMap(res => { 
   if (res.bearerToken) {
      return of(this.saveJwt(res.bearerToken)); 
   } 
   else {
      return throwError('Valid token not returned');  // this is 
   }
});

or more concisely:

this.httpPost.pipe(switchMap(res => (res.bearerToken) ? 
                                    of(this.saveJwt(res.bearerToken)) : 
                                    throwError('Valid token not returned')
));

The behavior will be the same, it's just a different syntax.

You're literally saying 'switch' from the HTTP observable in the pipe to a different observable, which is either just 'wrapping' the output value, or a new 'error' observable.

Don't forget to put of or you'll get some confusing error messages.

Also the beauty of 'switchMap' is that you can return a whole new 'chain' of commands if you wanted to - for whatever logic needs to be done with saveJwt.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • 8
    Once I started thinking of `switchMap` as an asynchronous `if` statement - things made a lot more sense :-) – Simon_Weaver Nov 19 '19 at 00:04
  • If you encounter `Importing this module is blacklisted. Try importing a submodule instead. (import-blacklist)tslint(1)`, then use `import { throwError } from 'rxjs/internal/observable/throwError';`, instead. – Cameron Hudson Aug 06 '21 at 18:16
  • 2
    A heads up that as of RxJS 8, `throwError(...)` where the parameter is a primitive (such as an error message) will be deprecated. Instead, pass an error factory function, i.e. `throwError(() => new Error(...))`. – marked-down Nov 12 '21 at 02:20
8

Even though this question is already answered, I'd like to share my own approach (even though its only slightly different from above).

I would decide what is returned separate from the mapping and vice versa. I'm not sure what operator is best for this so I'll use tap.

this.httpPost.pipe(
  tap(res => { 
    if (!res.bearerToken) {
      throw new Error('Valid token not returned');
    }
  }),
  map(res => this.saveJwt(res.bearerToken)),
);
christo8989
  • 6,442
  • 5
  • 37
  • 43
  • 1
    the return value of `tap` is ignored. this code does different thing than it says – s-f Mar 14 '19 at 13:25
  • I’m still getting used to rxjs. Would using switchMap be better? Can somebody suggest a different operator or edit directly? – christo8989 Mar 14 '19 at 13:27
  • I think that suggested `throw new Error()` is the best option so far – s-f Mar 14 '19 at 13:39
  • 2
    I was first skeptical about this approach but rxjs documentation has similar example https://rxjs.dev/api/operators/tap#example-2 – Petri Ryhänen Feb 03 '22 at 13:59
-1

Understand the difference of throwError() is not throw error https://medium.com/angular-in-depth/throwerror-is-not-throw-error-ad6c76c53377

jcdsr
  • 1,123
  • 1
  • 17
  • 35
  • 2
    A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it is there, then quote the most relevant part of the page you are linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](/help/deleted-answers) – 4b0 Jul 29 '21 at 11:58