Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJs Observable - Handle 404 by emitting default value

I am calling MS Graph to get a user's photo:

    // lets get the photo itself
    let photo$ = this.graph.get$(`users/${id}/photo/$value`, ResponseContentType.Blob)
        .map(resp => {
            return new Blob([resp.blob()], { type: resp.headers.get['Content-Type']}); })
        .map(photo => {
            let urlCreator = window.URL;
            return this.sanitizer
                .bypassSecurityTrustUrl(urlCreator.createObjectURL(photo));
        })
        .catch(function (e) {
            if(e.status === 404) {
                // no photo for this user!
                console.log(`No photo for user ${id}!  Returning default...`);
                return undefined;
            }
        })
        .toPromise();

However many users don't have a photo. In that case what I would like to do is create an object URL for some default photo (one of these perhaps) and emit that from the Observable. Or even just emit an undefined from the Observable, which I can handle in the front end by displaying some default image.

I know that my catch is firing, because I see this in the browser console:

No photo for user xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx! Returning default... (output redacted)

I'm actually pulling in many different bits of data for each user, each in its own Observable. I then convert each of those Observable to Promises, and then do a Promise.all() on all of them.

    // let's pull all of this together to form a ConcreteUser
    return Promise.all([photo$, ...])
        .then(values => {
            const photo = values[0];
            ...

The problem is in the case of a 404 for a user's photo, the whole Promise.all aborts, and I can't retrieve the user.

I've tried catching errors in the photo$ observable and emitting an undefined (which I planned to handle later), but it doesn't seem to be doing what I want.

How do I handle 404s in my Observable here, and emit something else (perhaps undefined) instead of killing the observable?

like image 956
John Dibling Avatar asked Mar 12 '17 17:03

John Dibling


2 Answers

I think I may have figured this out. A hint was lurking in the browser console...

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Reading the RxJs documentation on error handling showed them doing what I'm trying to do, so I was a bit confused by this. But then I noticed that in the documentation they call a mystery function getCachedVersion() which I assumed returned an actual cached thing. However if they instead returned an Observable.of() some actual cached thing, then that might explain it.

When I changed my catch code to:

        .catch(function (e) {
            if(e.status === 404) {
                // no photo for this user!
                console.log(`No photo for user ${id}!  Returning default...`);
                return Observable.of(undefined);
            }
        })

...it all started working.

like image 179
John Dibling Avatar answered Sep 29 '22 17:09

John Dibling


To translate Johns answer into an up-to-date rxjs implementation, this now looks like:

import { Observable, throwError, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
 
const observale: Observable<unknown>; 

observale.pipe(
   catchError(err => {
       if(err.error.statusCode === 404) {
          return of(undefined);
       } else {
          //important to use the rxjs error here not the standard one
          return throwError(err);
       }
   })
)
.subscribe(val => {
      console.log('success but may be undefined');
   }, err => {
      console.log('error');
});
like image 41
Liam Avatar answered Sep 29 '22 17:09

Liam