Sydney
Sydney

Reputation: 12212

Angular: Global Error Handler working only once

I have a service which calls a REST API. The create operation POST the parameters, and then GET the created object by using the id returned in the HTTP header. When the object is returned, a Subject propagates the object to notify other components. You can notice that I don't call the catch operation. I did it on purpose so the global error handler manages the error It works as expected the first time, but the second time, the global error handler is not called.

In the Chrome console, I get an error after the first call but not after the second call in rxjs/Subscriber.js:

Subscriber.js:240 Uncaught 
Response {_body: "{"message":"The query is not valid"}", status: 500, ok: false, statusText: "Internal Server Error", headers: Headers…}

SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) {
        try {
            fn.call(this._context, value);
        }
        catch (err) {
            this.unsubscribe();
            throw err;
        }
    };

The service:

@Injectable()
export class SystemService {

  private subject = new Subject<any>();

  constructor(private http: Http) {
  }

  create(system: SdqlSystem) {
    this.http.post(environment.apiEndpoint + 'systems', system)
      .flatMap(res => {
        const location = res.headers.get('Location');
        return this.http.get(location);
      })
      .map(res => res.json())
      .subscribe(data => this.subject.next(data));

  }

  get(id: string) {
    this.http.get(environment.apiEndpoint + 'systems/' + id)
      .map(res => res.json())
      .subscribe(data => this.subject.next(data));
  }

  systems(): Observable<any> {
    return this.subject.asObservable();
  }

}

The call from a form:

onSubmit({value, valid}: { value: SdqlSystem, valid: boolean }) {
  this.systemService.create(value);
}

The global error handler:

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {

  constructor(private logger: LoggingService, private notificationsService: NotificationsService) {
  }

  handleError(error: Response | any): void {
    console.log('***** HANDLE ERROR *****');
    let errMsg: string;
    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || body.message || JSON.stringify(body);
      errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    this.logger.error(errMsg);
    this.notificationsService.error('Error', errMsg);
    throw error;
  }

}

Upvotes: 3

Views: 2452

Answers (1)

mtx
mtx

Reputation: 1682

OP already resolved it, but I guess I'll answer in case someone else stumbles upon this problem:

Rethrowing the error in the GlobalErrorHandler leads to an uncaught exception where the browser stops execution. That's why it works only once.
The solution is simply to not rethrow in the ErrorHandler.

More detail: In this case there is no onError-function provided in subscribe(). That's why rxjs/Subscriber.js runs into the catch-part of __tryOrUnsub. It tries to execute the onError-function but fails (because there is none) and therefore unsubscribes from the Observable and throws the error. The GlobalErrorHandler picks it up, does logging and notification and then rethrows it.
At this point the error becomes an uncaught exception outside of Angular. (the default ErrorHandler does not rethrow an error)

Note: this approach works for standard http-calls, because the Response-Observable only emits one value and then completes anyway. If you have an Observable that needs to emit more than one value you will have to deal with the error differently because an Observable that emits an error will not emit any further values, as specified in the Observable-Contract:

OnError
indicates that the Observable has terminated with a specified error condition and that it will be emitting no further items

Upvotes: 12

Related Questions