How does HTTP error-handling work with observables?

codepleb picture codepleb · Jun 6, 2017 · Viewed 64.7k times · Source

I see a lot of tutorials doing something like this:

http.get("...").subscribe(
  success => console.log('hello success'),
  error => console.log('bye error')
);

I don't know how this works, since there aren't any types or anything, however I tried to do that myself and I end up that the request always goes into success, even if I have an error. What is the problem?

Troublemaker:

this.memberService.create(this.currentMember)
      .subscribe(
        success => {
          let mem: Member = success.json() as Member;
          if (this.selectedOrganization) {
            this.addMemberToOrganization(mem);
          } else if (this.selectedServiceProvider) {
            this.addMemberToServiceProvider(mem);
          } else {
            this.toastsService.error("lbl_users_service_provider_and_organization_undefined");
          }
        },
        error => console.log(error)
      );

Create-Method in the memberService:

  create(member: Member): Observable<any> {
    return this.http
      .post(this.RESOURCE_BASE_URL, member)
      .map(response => {
        if (response.status === 200) this.toastsSerivce.success(this.translateService.instant('lbl_users_member_created'));
        return response;
      })
      .catch(error => this.toastsSerivce.error(this.translateService.instant('lbl_users_member_create_failed')));
  }

I even catch the error, but the subscribe part doesn't seem to care. It fails at success.json(), because if there is an error, there is no json. But if there is an error, I want it to call the error =>... instead of the success. Any advice is highly appreciated.

Answer

SrAxi picture SrAxi · Jun 6, 2017

I think the issue is that you are not throwing the error with an Observable.throw(errMsg).

So, you may just use it like this:

.catch((error:any) => Observable.throw(error.json().error || 'Server error'));

In your example:

create(member: Member): Observable<any> {
    return this.http
      .post(this.RESOURCE_BASE_URL, member)
      .map(response => {
        if (response.status === 200) this.toastsSerivce.success(this.translateService.instant('lbl_users_member_created'));
        return response;
      })
      .catch((error:any) => Observable.throw(this.toastsSerivce.error(this.translateService.instant('lbl_users_member_create_failed'))));
  }

But, you could use an error handler, like the one Angular proposes here:

private handleError (error: Response | any) {
    // In a real world app, you might use a remote logging infrastructure
    let errMsg: string;
    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || JSON.stringify(body);
      errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    console.error(errMsg);
    return Observable.throw(errMsg);
  }

And so, your method would look more like this:

create(member: Member): Observable<any> {
    return this.http
      .post(this.RESOURCE_BASE_URL, member)
      .map(response => {
        if (response.status === 200) this.toastsSerivce.success(this.translateService.instant('lbl_users_member_created'));
        return response;
      })
      .catch(this.handleError);
  }

It's actually cleaner and more reusable for other methods that you may create within your service.

I would suggest to use also a response handler, like the one used by Angular's devs: this.extractData.

Obviusly, inside the error handle method you can put your own custom logic, depends on how you want to show or handle the error.

NOTE: I did not test your code nor the code I posted here. But I wanted to show/express the concept. You should throw the error in order to not going into success everytime. How you handle it depends on you and your App.