148

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?

10
  • 4
    What about throw 'Valid token not returned';? Commented Apr 4, 2017 at 6:18
  • Fails to compile Commented Apr 4, 2017 at 6:19
  • Exact error message please. Commented Apr 4, 2017 at 6:20
  • 2
    Oh sorry, it doesn't work with return throw 'message here' but does work without the return keyword. Let me check if its working correct logically. Commented Apr 4, 2017 at 6:22
  • 1
    The error text is not being received in the subscribe method and the .finally() in the stream also triggers. (However the execution is stopped which is a good thing) Commented Apr 4, 2017 at 6:28

4 Answers 4

207

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

Sign up to request clarification or add additional context in comments.

7 Comments

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!
@martin: Could you please develop why we wouldn't want to you .catch() here?
@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()
Is this identical to return throwError(new Error('Valid token not returned')); ?
@Simon_Weaver no it's not. return throwError() returns an Observable<never>, this just interrupts the observable stream immediately, without returning at all.
|
54

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.

3 Comments

Once I started thinking of switchMap as an asynchronous if statement - things made a lot more sense :-)
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.
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(...)).
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)),
);

4 Comments

the return value of tap is ignored. this code does different thing than it says
I’m still getting used to rxjs. Would using switchMap be better? Can somebody suggest a different operator or edit directly?
I think that suggested throw new Error() is the best option so far
I was first skeptical about this approach but rxjs documentation has similar example rxjs.dev/api/operators/tap#example-2
-1

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

1 Comment

A link to a solution is welcome, but please ensure your answer is useful without it: add context around the link 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.