refactor(injector): change reflector to collect the resolving path only when an error occurs

This commit is contained in:
vsavkin 2014-10-03 17:26:49 -04:00
parent 15305b6cd7
commit f63a5dd158
6 changed files with 74 additions and 47 deletions

View File

@ -1,23 +1,27 @@
import {ListWrapper, List} from 'facade/collection'; import {ListWrapper, List} from 'facade/collection';
import {stringify} from 'facade/lang'; import {stringify} from 'facade/lang';
import {Key} from './key';
function constructResolvingPath(keys: List) { function constructResolvingPath(keys: List) {
if (keys.length > 1) { if (keys.length > 1) {
var tokenStrs = ListWrapper.map(keys, (k) => stringify(k.token)); var reversed = ListWrapper.reversed(keys);
var tokenStrs = ListWrapper.map(reversed, (k) => stringify(k.token));
return " (" + tokenStrs.join(' -> ') + ")"; return " (" + tokenStrs.join(' -> ') + ")";
} else { } else {
return ""; return "";
} }
} }
export class NoProviderError extends Error { export class ProviderError extends Error {
constructor(keys:List){ constructor(key:Key, constructResolvingMessage:Function){
this.message = this._constructResolvingMessage(keys); this.keys = [key];
this.constructResolvingMessage = constructResolvingMessage;
this.message = this.constructResolvingMessage(this.keys);
} }
_constructResolvingMessage(keys:List) { addKey(key: Key) {
var last = stringify(ListWrapper.last(keys).token); ListWrapper.push(this.keys, key);
return `No provider for ${last}!${constructResolvingPath(keys)}`; this.message = this.constructResolvingMessage(this.keys);
} }
toString() { toString() {
@ -25,19 +29,22 @@ export class NoProviderError extends Error {
} }
} }
export class AsyncProviderError extends Error { export class NoProviderError extends ProviderError {
constructor(keys:List){ constructor(key:Key){
this.message = this._constructResolvingMessage(keys); super(key, function(keys:List) {
var first = stringify(ListWrapper.first(keys).token);
return `No provider for ${first}!${constructResolvingPath(keys)}`;
});
} }
}
_constructResolvingMessage(keys:List) { export class AsyncProviderError extends ProviderError {
var last = stringify(ListWrapper.last(keys).token); constructor(key:Key){
return `Cannot instantiate ${last} synchronously. ` + super(key, function(keys:List) {
`It is provided as a future!${constructResolvingPath(keys)}`; var first = stringify(ListWrapper.first(keys).token);
} return `Cannot instantiate ${first} synchronously. ` +
`It is provided as a future!${constructResolvingPath(keys)}`;
toString() { });
return this.message;
} }
} }

View File

@ -1,6 +1,6 @@
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 {NoProviderError, InvalidBindingError, AsyncProviderError} from './exceptions'; import {ProviderError, NoProviderError, InvalidBindingError, AsyncProviderError} 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';
@ -32,19 +32,15 @@ export class Injector {
} }
getByKey(key:Key) { getByKey(key:Key) {
return this._getByKey(key, [], false); return this._getByKey(key, false);
} }
asyncGetByKey(key:Key) { asyncGetByKey(key:Key) {
return this._getByKey(key, [], true); return this._getByKey(key, true);
} }
_getByKey(key:Key, resolving:List, async) { _getByKey(key:Key, async) {
var keyId = key.id; var keyId = key.id;
//TODO: vsavkin: use LinkedList to remove clone
resolving = ListWrapper.clone(resolving)
ListWrapper.push(resolving, key);
if (key.token === Injector) return this._injector(async); if (key.token === Injector) return this._injector(async);
var instance = this._get(this._instances, keyId); var instance = this._get(this._instances, keyId);
@ -53,14 +49,14 @@ export class Injector {
var binding = this._get(this._bindings, keyId); var binding = this._get(this._bindings, keyId);
if (isPresent(binding)) { if (isPresent(binding)) {
return this._instantiate(key, binding, resolving, async); return this._instantiate(key, binding, async);
} }
if (isPresent(this._parent)) { if (isPresent(this._parent)) {
return this._parent._getByKey(key, resolving, async); return this._parent._getByKey(key, async);
} }
throw new NoProviderError(resolving); throw new NoProviderError(key);
} }
createChild(bindings:List):Injector { createChild(bindings:List):Injector {
@ -78,31 +74,37 @@ export class Injector {
return ListWrapper.get(list, index); return ListWrapper.get(list, index);
} }
_instantiate(key:Key, binding:Binding, resolving:List, async) { _instantiate(key:Key, binding:Binding, async) {
if (binding.async && !async) { if (binding.async && !async) {
throw new AsyncProviderError(resolving); throw new AsyncProviderError(key);
} }
if (async) { if (async) {
return this._instantiateAsync(key, binding, resolving, async); return this._instantiateAsync(key, binding, async);
} else { } else {
return this._instantiateSync(key, binding, resolving, async); return this._instantiateSync(key, binding, async);
} }
} }
_instantiateSync(key:Key, binding:Binding, resolving:List, async) { _instantiateSync(key:Key, binding:Binding, async) {
var deps = ListWrapper.map(binding.dependencies, d => this._getByKey(d, resolving, false)); try {
var instance = binding.factory(deps); var deps = ListWrapper.map(binding.dependencies, d => this._getByKey(d, false));
ListWrapper.set(this._instances, key.id, instance); var instance = binding.factory(deps);
if (!binding.async && async) { ListWrapper.set(this._instances, key.id, instance);
return FutureWrapper.value(instance); if (!binding.async && async) {
return FutureWrapper.value(instance);
}
return instance;
} catch (e) {
if (e instanceof ProviderError) e.addKey(key);
throw e;
} }
return instance;
} }
_instantiateAsync(key:Key, binding:Binding, resolving:List, async):Future { _instantiateAsync(key:Key, binding:Binding, async):Future {
var instances = this._createInstances(); var instances = this._createInstances();
var futures = ListWrapper.map(binding.dependencies, d => this._getByKey(d, resolving, true)); var futures = ListWrapper.map(binding.dependencies, d => this._getByKey(d, true));
return FutureWrapper.wait(futures). return FutureWrapper.wait(futures).
then(binding.factory). then(binding.factory).
then(function(instance) { then(function(instance) {

View File

@ -72,5 +72,7 @@ export function main () {
expect(() => injector.get(UserController)) expect(() => injector.get(UserController))
.toThrowError('Cannot instantiate UserList synchronously. It is provided as a future! (UserController -> UserList)'); .toThrowError('Cannot instantiate UserList synchronously. It is provided as a future! (UserController -> UserList)');
}); });
// resolve exceptions and async
}); });
} }

View File

@ -2,7 +2,10 @@ import {describe, it, 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 Dashboard {} class DashboardSoftware {}
class Dashboard {
constructor(software: DashboardSoftware){}
}
class TurboEngine extends Engine{} class TurboEngine extends Engine{}
class Car { class Car {
@ -135,10 +138,9 @@ export function main() {
}); });
it('should show the full path when no provider', function() { it('should show the full path when no provider', function() {
var injector = new Injector([CarWithDashboard, Engine]); var injector = new Injector([CarWithDashboard, Engine, Dashboard]);
expect(() => injector.get(CarWithDashboard)). expect(() => injector.get(CarWithDashboard)).
toThrowError('No provider for Dashboard! (CarWithDashboard -> Dashboard)'); toThrowError('No provider for DashboardSoftware! (CarWithDashboard -> Dashboard -> DashboardSoftware)');
}); });
}); });
} }

View File

@ -24,9 +24,15 @@ class ListWrapper {
static forEach(list, fn) { static forEach(list, fn) {
list.forEach(fn); list.forEach(fn);
} }
static last(list) { static first(List list) {
return list.first;
}
static last(List list) {
return list.last; return list.last;
} }
static reversed(List list) {
return list.reversed;
}
static void push(List l, e) { l.add(e); } static void push(List l, e) { l.add(e); }
} }

View File

@ -34,10 +34,18 @@ export class ListWrapper {
static push(array, el) { static push(array, el) {
array.push(el); array.push(el);
} }
static first(array) {
if (!array) return null;
return array[0];
}
static last(array) { static last(array) {
if (!array || array.length == 0) return null; if (!array || array.length == 0) return null;
return array[array.length - 1]; return array[array.length - 1];
} }
static reversed(array) {
var a = ListWrapper.clone(array);
return a.reverse();
}
} }
export class SetWrapper { export class SetWrapper {