refactor(exception_handler): unified all exception handling

BREAKING CHANGE
    Previously it was possible to pass a custom error reporter to bootstrap, which was used only during the construction of Injector. This had limited utility, so this capability has been removed.
This commit is contained in:
vsavkin 2015-07-22 17:15:38 -07:00
parent fdf226ab69
commit 70792c744d
7 changed files with 23 additions and 41 deletions

View File

@ -15,7 +15,7 @@ export 'application_common.dart' show ApplicationRef;
/// ///
/// See [commonBootstrap] for detailed documentation. /// See [commonBootstrap] for detailed documentation.
Future<ApplicationRef> bootstrap(Type appComponentType, Future<ApplicationRef> bootstrap(Type appComponentType,
[List componentInjectableBindings, Function errorReporter]) { [List componentInjectableBindings]) {
reflector.reflectionCapabilities = new ReflectionCapabilities(); reflector.reflectionCapabilities = new ReflectionCapabilities();
return commonBootstrap(appComponentType, componentInjectableBindings, errorReporter); return commonBootstrap(appComponentType, componentInjectableBindings);
} }

View File

@ -97,8 +97,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
bind(appComponentType) bind(appComponentType)
.toFactory((p: Promise<any>) => p.then(ref => ref.instance), [appComponentRefPromiseToken]), .toFactory((p: Promise<any>) => p.then(ref => ref.instance), [appComponentRefPromiseToken]),
bind(LifeCycle) bind(LifeCycle).toFactory((exceptionHandler) => new LifeCycle(null, assertionsEnabled()),
.toFactory((exceptionHandler) => new LifeCycle(exceptionHandler, null, assertionsEnabled()),
[ExceptionHandler]), [ExceptionHandler]),
bind(EventManager) bind(EventManager)
.toFactory( .toFactory(
@ -141,22 +140,13 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
]; ];
} }
function _createNgZone(givenReporter: Function): NgZone { function _createNgZone(): NgZone {
var defaultErrorReporter = (exception, stackTrace) => { // bootstrapErrorReporter is needed because we cannot use custom exception handler
var longStackTrace = ListWrapper.join(stackTrace, "\n\n-----async gap-----\n"); // configured via DI until the root Injector has been created.
DOM.logError(`${exception}\n\n${longStackTrace}`); var handler = new ExceptionHandler();
var bootstrapErrorReporter = (exception, stackTrace) => handler.call(exception, stackTrace);
if (exception instanceof BaseException && isPresent(exception.context)) {
print("Error Context:");
print(exception.context);
}
throw exception;
};
var reporter = isPresent(givenReporter) ? givenReporter : defaultErrorReporter;
var zone = new NgZone({enableLongStackTrace: assertionsEnabled()}); var zone = new NgZone({enableLongStackTrace: assertionsEnabled()});
zone.overrideOnErrorHandler(reporter); zone.overrideOnErrorHandler(bootstrapErrorReporter);
return zone; return zone;
} }
@ -287,17 +277,20 @@ function _createNgZone(givenReporter: Function): NgZone {
* Returns a `Promise` of {@link ApplicationRef}. * Returns a `Promise` of {@link ApplicationRef}.
*/ */
export function commonBootstrap( export function commonBootstrap(
appComponentType: Type, componentInjectableBindings: List<Type | Binding | List<any>> = null, appComponentType: Type, componentInjectableBindings: List<Type | Binding | List<any>> = null):
errorReporter: Function = null): Promise<ApplicationRef> { Promise<ApplicationRef> {
BrowserDomAdapter.makeCurrent(); BrowserDomAdapter.makeCurrent();
var bootstrapProcess = PromiseWrapper.completer(); var bootstrapProcess = PromiseWrapper.completer();
var zone = _createNgZone(errorReporter); var zone = _createNgZone();
zone.run(() => { zone.run(() => {
// TODO(rado): prepopulate template cache, so applications with only // TODO(rado): prepopulate template cache, so applications with only
// index.html and main.js are possible. // index.html and main.js are possible.
var appInjector = _createAppInjector(appComponentType, componentInjectableBindings, zone); var appInjector = _createAppInjector(appComponentType, componentInjectableBindings, zone);
var exceptionHandler = appInjector.get(ExceptionHandler);
zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s));
var compRefToken: Promise<any> = var compRefToken: Promise<any> =
PromiseWrapper.wrap(() => appInjector.get(appComponentRefPromiseToken)); PromiseWrapper.wrap(() => appInjector.get(appComponentRefPromiseToken));
var tick = (componentRef) => { var tick = (componentRef) => {

View File

@ -8,7 +8,6 @@ import 'application_common.dart';
/// See [commonBootstrap] for detailed documentation. /// See [commonBootstrap] for detailed documentation.
Future<ApplicationRef> bootstrapStatic( Future<ApplicationRef> bootstrapStatic(
Type appComponentType, Type appComponentType,
[List componentInjectableBindings, [List componentInjectableBindings]) {
Function errorReporter]) { return commonBootstrap(appComponentType, componentInjectableBindings);
return commonBootstrap(appComponentType, componentInjectableBindings, errorReporter);
} }

View File

@ -28,7 +28,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
export class ExceptionHandler { export class ExceptionHandler {
logError: Function = DOM.logError; logError: Function = DOM.logError;
call(exception: Object, stackTrace: string | string[] = null, reason: string = null) { call(exception: Object, stackTrace: any = null, reason: string = null) {
var longStackTrace = isListLikeIterable(stackTrace) ? var longStackTrace = isListLikeIterable(stackTrace) ?
(<any>stackTrace).join("\n\n-----async gap-----\n") : (<any>stackTrace).join("\n\n-----async gap-----\n") :
stackTrace; stackTrace;

View File

@ -1,7 +1,6 @@
import {Injectable} from 'angular2/di'; import {Injectable} from 'angular2/di';
import {ChangeDetector} from 'angular2/change_detection'; import {ChangeDetector} from 'angular2/change_detection';
import {NgZone} from 'angular2/src/core/zone/ng_zone'; import {NgZone} from 'angular2/src/core/zone/ng_zone';
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {isPresent, BaseException} from 'angular2/src/facade/lang'; import {isPresent, BaseException} from 'angular2/src/facade/lang';
/** /**
@ -32,17 +31,11 @@ import {isPresent, BaseException} from 'angular2/src/facade/lang';
*/ */
@Injectable() @Injectable()
export class LifeCycle { export class LifeCycle {
_errorHandler;
_changeDetector: ChangeDetector; _changeDetector: ChangeDetector;
_enforceNoNewChanges: boolean; _enforceNoNewChanges: boolean;
_runningTick: boolean = false; _runningTick: boolean = false;
constructor(exceptionHandler: ExceptionHandler, changeDetector: ChangeDetector = null, constructor(changeDetector: ChangeDetector = null, enforceNoNewChanges: boolean = false) {
enforceNoNewChanges: boolean = false) {
this._errorHandler = (exception, stackTrace) => {
exceptionHandler.call(exception, stackTrace);
throw exception;
};
this._changeDetector = this._changeDetector =
changeDetector; // may be null when instantiated from application bootstrap changeDetector; // may be null when instantiated from application bootstrap
this._enforceNoNewChanges = enforceNoNewChanges; this._enforceNoNewChanges = enforceNoNewChanges;
@ -55,8 +48,6 @@ export class LifeCycle {
if (isPresent(changeDetector)) { if (isPresent(changeDetector)) {
this._changeDetector = changeDetector; this._changeDetector = changeDetector;
} }
zone.overrideOnErrorHandler(this._errorHandler);
zone.overrideOnTurnDone(() => this.tick()); zone.overrideOnTurnDone(() => this.tick());
} }

View File

@ -83,8 +83,7 @@ export function main() {
it('should throw if bootstrapped Directive is not a Component', it('should throw if bootstrapped Directive is not a Component',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
var refPromise = var refPromise = bootstrap(HelloRootDirectiveIsNotCmp, testBindings);
bootstrap(HelloRootDirectiveIsNotCmp, testBindings, (e, t) => { throw e; });
PromiseWrapper.then(refPromise, null, (reason) => { PromiseWrapper.then(refPromise, null, (reason) => {
expect(reason.message) expect(reason.message)
@ -96,7 +95,7 @@ export function main() {
})); }));
it('should throw if no element is found', inject([AsyncTestCompleter], (async) => { it('should throw if no element is found', inject([AsyncTestCompleter], (async) => {
var refPromise = bootstrap(HelloRootCmp, [], (e, t) => { throw e; }); var refPromise = bootstrap(HelloRootCmp, []);
PromiseWrapper.then(refPromise, null, (reason) => { PromiseWrapper.then(refPromise, null, (reason) => {
expect(reason.message).toContain('The selector "hello-app" did not match any elements'); expect(reason.message).toContain('The selector "hello-app" did not match any elements');
async.done(); async.done();

View File

@ -21,7 +21,7 @@ export function main() {
describe("LifeCycle", () => { describe("LifeCycle", () => {
it("should throw when reentering tick", () => { it("should throw when reentering tick", () => {
var cd = <any>new SpyChangeDetector(); var cd = <any>new SpyChangeDetector();
var lc = new LifeCycle(null, cd, false); var lc = new LifeCycle(cd, false);
cd.spy("detectChanges").andCallFake(() => lc.tick()); cd.spy("detectChanges").andCallFake(() => lc.tick());
expect(() => lc.tick()).toThrowError("LifeCycle.tick is called recursively"); expect(() => lc.tick()).toThrowError("LifeCycle.tick is called recursively");