feat(injector): handle construction errors
- Detect cyclic deps - Handle initialization errors
This commit is contained in:
parent
14af5a0a42
commit
4d6c7481ad
|
@ -50,6 +50,24 @@ export class AsyncBindingError extends ProviderError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CyclicDependencyError extends ProviderError {
|
||||||
|
constructor(key:Key){
|
||||||
|
super(key, function(keys:List) {
|
||||||
|
return `Cannot instantiate cyclic dependency!${constructResolvingPath(keys)}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InstantiationError extends ProviderError {
|
||||||
|
constructor(originalException, key:Key){
|
||||||
|
super(key, function(keys:List) {
|
||||||
|
var first = stringify(ListWrapper.first(keys).token);
|
||||||
|
return `Error during instantiation of ${first}!${constructResolvingPath(keys)}.`+
|
||||||
|
` ORIGINAL ERROR: ${originalException}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class InvalidBindingError extends DIError {
|
export class InvalidBindingError extends DIError {
|
||||||
constructor(binding){
|
constructor(binding){
|
||||||
this.message = `Invalid binding ${binding}`;
|
this.message = `Invalid binding ${binding}`;
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import {Map, List, MapWrapper, ListWrapper} from 'facade/collection';
|
import {Map, List, MapWrapper, ListWrapper} from 'facade/collection';
|
||||||
import {Binding, BindingBuilder, bind} from './binding';
|
import {Binding, BindingBuilder, bind} from './binding';
|
||||||
import {ProviderError, NoProviderError, InvalidBindingError, AsyncBindingError} from './exceptions';
|
import {ProviderError, NoProviderError, InvalidBindingError,
|
||||||
|
AsyncBindingError, CyclicDependencyError, InstantiationError} from './exceptions';
|
||||||
import {Type, isPresent, isBlank} from 'facade/lang';
|
import {Type, isPresent, isBlank} from 'facade/lang';
|
||||||
import {Future, FutureWrapper} from 'facade/async';
|
import {Future, FutureWrapper} from 'facade/async';
|
||||||
import {Key} from './key';
|
import {Key} from './key';
|
||||||
|
|
||||||
|
var _creating = new Object();
|
||||||
|
|
||||||
export class Injector {
|
export class Injector {
|
||||||
constructor(bindings:List) {
|
constructor(bindings:List) {
|
||||||
var flatten = _flattenBindings(bindings);
|
var flatten = _flattenBindings(bindings);
|
||||||
|
@ -44,6 +47,9 @@ export class Injector {
|
||||||
if (key.token === Injector) return this._injector(returnFuture);
|
if (key.token === Injector) return this._injector(returnFuture);
|
||||||
|
|
||||||
var instance = this._get(this._instances, keyId);
|
var instance = this._get(this._instances, keyId);
|
||||||
|
if (instance === _creating) {
|
||||||
|
throw new CyclicDependencyError(key);
|
||||||
|
}
|
||||||
if (isPresent(instance)) return instance;
|
if (isPresent(instance)) return instance;
|
||||||
|
|
||||||
var binding = this._get(this._bindings, keyId);
|
var binding = this._get(this._bindings, keyId);
|
||||||
|
@ -73,6 +79,8 @@ export class Injector {
|
||||||
}
|
}
|
||||||
|
|
||||||
_instantiate(key:Key, binding:Binding, returnFuture) {
|
_instantiate(key:Key, binding:Binding, returnFuture) {
|
||||||
|
ListWrapper.set(this._instances, key.id, _creating);
|
||||||
|
|
||||||
if (binding.providedAsFuture && !returnFuture) {
|
if (binding.providedAsFuture && !returnFuture) {
|
||||||
throw new AsyncBindingError(key);
|
throw new AsyncBindingError(key);
|
||||||
}
|
}
|
||||||
|
@ -85,14 +93,20 @@ export class Injector {
|
||||||
}
|
}
|
||||||
|
|
||||||
_instantiateSync(key:Key, binding:Binding) {
|
_instantiateSync(key:Key, binding:Binding) {
|
||||||
|
var deps;
|
||||||
|
try {
|
||||||
|
deps = ListWrapper.map(binding.dependencies, d => this._getByKey(d.key, d.asFuture));
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ProviderError) e.addKey(key);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var deps = ListWrapper.map(binding.dependencies, d => this._getByKey(d.key, d.asFuture));
|
|
||||||
var instance = binding.factory(deps);
|
var instance = binding.factory(deps);
|
||||||
ListWrapper.set(this._instances, key.id, instance);
|
ListWrapper.set(this._instances, key.id, instance);
|
||||||
return instance;
|
return instance;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ProviderError) e.addKey(key);
|
throw new InstantiationError(e, key);
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,11 +115,6 @@ export class Injector {
|
||||||
var futures = ListWrapper.map(binding.dependencies, d => this._getByKey(d.key, true));
|
var futures = ListWrapper.map(binding.dependencies, d => this._getByKey(d.key, true));
|
||||||
return FutureWrapper.wait(futures).
|
return FutureWrapper.wait(futures).
|
||||||
then(binding.factory).
|
then(binding.factory).
|
||||||
catch(function(e) {
|
|
||||||
console.log('sdfsdfsd', e)
|
|
||||||
//e.addKey(key)
|
|
||||||
//return e;
|
|
||||||
}).
|
|
||||||
then(function(instance) {
|
then(function(instance) {
|
||||||
ListWrapper.set(instances, key.id, instance);
|
ListWrapper.set(instances, key.id, instance);
|
||||||
return instance
|
return instance
|
||||||
|
|
|
@ -2,6 +2,11 @@ import {describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||||
import {Injector, Inject, bind} from 'di/di';
|
import {Injector, Inject, bind} from 'di/di';
|
||||||
|
|
||||||
class Engine {}
|
class Engine {}
|
||||||
|
class BrokenEngine {
|
||||||
|
constructor() {
|
||||||
|
throw "Broken Engine";
|
||||||
|
}
|
||||||
|
}
|
||||||
class DashboardSoftware {}
|
class DashboardSoftware {}
|
||||||
class Dashboard {
|
class Dashboard {
|
||||||
constructor(software: DashboardSoftware){}
|
constructor(software: DashboardSoftware){}
|
||||||
|
@ -33,6 +38,10 @@ class CarWithInject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CyclicEngine {
|
||||||
|
constructor(car:Car){}
|
||||||
|
}
|
||||||
|
|
||||||
class NoAnnotations {
|
class NoAnnotations {
|
||||||
constructor(secretDependency){}
|
constructor(secretDependency){}
|
||||||
}
|
}
|
||||||
|
@ -151,5 +160,29 @@ export function main() {
|
||||||
expect(() => injector.get(CarWithDashboard)).
|
expect(() => injector.get(CarWithDashboard)).
|
||||||
toThrowError('No provider for DashboardSoftware! (CarWithDashboard -> Dashboard -> DashboardSoftware)');
|
toThrowError('No provider for DashboardSoftware! (CarWithDashboard -> Dashboard -> DashboardSoftware)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw when trying to instantiate a cyclic dependency', function() {
|
||||||
|
var injector = new Injector([
|
||||||
|
Car,
|
||||||
|
bind(Engine).toClass(CyclicEngine)
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(() => injector.get(Car))
|
||||||
|
.toThrowError('Cannot instantiate cyclic dependency! (Car -> Engine -> Car)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the full path when error happens in a constructor', function() {
|
||||||
|
var injector = new Injector([
|
||||||
|
Car,
|
||||||
|
bind(Engine).toClass(BrokenEngine)
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
injector.get(Car);
|
||||||
|
throw "Must throw";
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toContain("Error during instantiation of Engine! (Car -> Engine)");
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
Loading…
Reference in New Issue