How to throw error from RxJS map operator (angular)

AngularTypescriptRxjs

Angular Problem Overview


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?

Angular Solutions


Solution 1 - Angular

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

Solution 2 - Angular

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.

Solution 3 - Angular

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)),
);

Solution 4 - Angular

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionHassanView Question on Stackoverflow
Solution 1 - AngularmartinView Answer on Stackoverflow
Solution 2 - AngularSimon_WeaverView Answer on Stackoverflow
Solution 3 - Angularchristo8989View Answer on Stackoverflow
Solution 4 - AngularjcdsrView Answer on Stackoverflow