Closed smorks closed 8 years ago
That's an Angular thing. A rejected promise will bubble up to the global exception handler, unless you catch it somewhere and turn the response into a resolved promise, but that's often not what you want, because you don't want upstream success logic to be executed in an error case.
The way around this is not to catch an error at a lower level if you can't do anything about it and instead let it bubble up to the global exception handler and deal with it there.
Another option is to mark the error as handled, so that you can ignore it in the global exception handler.
Here's a snippet. I typically have an error handler, that deals with all my errors. It does things like logging them, turning the error message into a user friendly message etc. Whenever I catch an error somewhere, I pass it to the error handler. If the error has already been handled previously, it simply ignores it.
Example ErrorHandler:
import { Injectable} from '@angular/core';
import { EntityError } from 'breeze-client';
import * as Raven from 'raven-js';
import { DialogService } from './dialog.service';
// Configure Raven
Raven.config(SiteInfo.ravenDsn).install();
export enum ErrorLevel {
Exception, Info, Warning
};
@Injectable()
export class ErrorHandler {
constructor(private dialogService: DialogService) { }
// Handles error and returns a rejected promise.
error(e: string | any): void {
this.handle(e, ErrorLevel.Exception);
}
warning(e: string | any): void {
this.handle(e, ErrorLevel.Warning);
}
info(e: string | any): void {
this.handle(e, ErrorLevel.Info);
}
private handle(e: string | any, errorLevel?: ErrorLevel) {
// Ignore error if it was already handled once.
if (e.errorWasHandled) return;
errorLevel = errorLevel || ErrorLevel.Exception;
let message = typeof e === 'string' ? e : this.userFriendlyErrorMessage(e);
let entityErrors = this.formatEntityErrors(e.entityErrors);
if (entityErrors && entityErrors !== '') {
message = `${message}\n\n${entityErrors}`;
}
let title = this.errorLevelToTitle(errorLevel);
let buttonsNames = this.getApplicableButtonNamesByError(e);
this.dialogService.messageBox(title, message, buttonsNames).then(result => {
if (result == 'Login') {
document.location.reload();
}
});
let consoleMessage = `${title.toUpperCase()}: ${message}`;
if (errorLevel === ErrorLevel.Exception) {
console.error(consoleMessage);
} else if (errorLevel === ErrorLevel.Warning) {
console.warn(consoleMessage);
} else if (errorLevel === ErrorLevel.Info) {
console.info(consoleMessage);
} else {
console.log(consoleMessage);
}
if (errorLevel === ErrorLevel.Exception) {
Raven.captureException(e);
}
// Mark error as handled
e.errorWasHandled = true;
}
private userFriendlyErrorMessage(e: any) {
if (this.isHttpStatus(e, 401)) {
e.message = 'You have been logged out due to inactivity. To continue, click on the Login button below and re-enter your credentials';
}
return e.message || 'An unexpected error has occurred.';
}
private getApplicableButtonNamesByError(e: any): string[] {
if (this.isHttpStatus(e, 401)) {
return ['Login'];
}
return ['Ok']; // Return the default Ok button for most errors.
}
private isHttpStatus(e: any, status: number) {
return _.get(e, 'httpResponse.status', 200) == status;
}
private formatEntityErrors(entityErrors: EntityError[]) {
let s = '';
if (!entityErrors || !entityErrors.length) return s;
entityErrors.forEach(ee => {
s += `- ${ee.errorMessage}\n`;
});
return s.trim();
}
private errorLevelToTitle(errorLevel: ErrorLevel): string {
switch (errorLevel) {
case ErrorLevel.Exception:
return 'Error';
case ErrorLevel.Info:
return 'Information';
case ErrorLevel.Warning:
return 'Warning';
}
return 'Message';
}
}
And my global exception handler also passes the error to the ErrorHandler.
class CustomExceptionHandler {
call(exception: any, stackTrace: any = null, reason: string = null) {
// Pass exception to error handler.
let error = exception.rejection || exception.originalException || exception;
errorHandler.error(error);
}
}
Thanks for the detailed explanation!
Quick update on this one. Turns out there was actually a bug in the bridge. There was an errant rejected promise being created in the failure case, which bubbled up to the global exception handler. I fixed the issue in the latest version.
Note, that the latest version requires RC5 and the bridge is now an NgModule and not and Injectable anymore.
i tried out the latest version, and it works as expected! thanks! no more special handling for this in our global exception handler!
I'm having an issue trying to properly catch an 4xx error when executing a query.
I've created a sample to illustrate my problem.
What's happening is that it appears that only the first time that a breeze query is called, even when catching the error, the error is still bubbling up to my GlobalExceptionHandler with a "Uncaught (in promise)" error.
Am I missing something here?