feat(injector): handle async cyclic dependencies

This commit is contained in:
vsavkin 2014-10-06 16:24:12 -04:00
parent a0176273c5
commit e7666d0612
2 changed files with 29 additions and 13 deletions

View File

@ -6,14 +6,15 @@ 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';
class _InProgress { var _constructing = new Object();
class _Waiting {
constructor(future:Future) { constructor(future:Future) {
this.future = future; this.future = future;
} }
} }
var _isInProgressSync = new _InProgress(null); function _isWaiting(obj) {
function _isInProgress(obj) { return obj instanceof _Waiting;
return obj instanceof _InProgress;
} }
@ -108,10 +109,9 @@ class _SyncInjectorStrategy {
var instance = this.injector._getInstance(key); var instance = this.injector._getInstance(key);
// skip async in-progress if (instance === _constructing) {
if (instance === _isInProgressSync) {
throw new CyclicDependencyError(key); throw new CyclicDependencyError(key);
} else if (isPresent(instance) && !_isInProgress(instance)) { } else if (isPresent(instance) && !_isWaiting(instance)) {
return instance; return instance;
} else { } else {
return null; return null;
@ -125,7 +125,7 @@ class _SyncInjectorStrategy {
if (binding.providedAsFuture) throw new AsyncBindingError(key); if (binding.providedAsFuture) throw new AsyncBindingError(key);
//add a marker so we can detect cyclic dependencies //add a marker so we can detect cyclic dependencies
this.injector._setInstance(key, _isInProgressSync); this.injector._setInstance(key, _constructing);
var deps = this._resolveDependencies(key, binding); var deps = this._resolveDependencies(key, binding);
return this._createInstance(key, binding, deps); return this._createInstance(key, binding, deps);
@ -136,6 +136,7 @@ class _SyncInjectorStrategy {
var getDependency = d => this.injector._getByKey(d.key, d.asFuture, d.lazy); var getDependency = d => this.injector._getByKey(d.key, d.asFuture, d.lazy);
return ListWrapper.map(binding.dependencies, getDependency); return ListWrapper.map(binding.dependencies, getDependency);
} catch (e) { } catch (e) {
this.injector._setInstance(key, null);
if (e instanceof ProviderError) e.addKey(key); if (e instanceof ProviderError) e.addKey(key);
throw e; throw e;
} }
@ -165,7 +166,10 @@ class _AsyncInjectorStrategy {
} }
var instance = this.injector._getInstance(key); var instance = this.injector._getInstance(key);
if (_isInProgress(instance)) {
if (instance === _constructing) {
throw new CyclicDependencyError(key);
} else if (_isWaiting(instance)) {
return instance.future; return instance.future;
} else if (isPresent(instance)) { } else if (isPresent(instance)) {
return FutureWrapper.value(instance); return FutureWrapper.value(instance);
@ -178,23 +182,32 @@ class _AsyncInjectorStrategy {
var binding = this.injector._getBinding(key); var binding = this.injector._getBinding(key);
if (isBlank(binding)) return null; if (isBlank(binding)) return null;
//add a marker so we can detect cyclic dependencies
this.injector._setInstance(key, _constructing);
var deps = this._resolveDependencies(key, binding); var deps = this._resolveDependencies(key, binding);
var future = FutureWrapper.wait(deps). var future = FutureWrapper.wait(deps).
then(deps => this._findOrCreate(key, binding, deps)). then(deps => this._findOrCreate(key, binding, deps)).
then(instance => this._cacheInstance(key, instance)); then(instance => this._cacheInstance(key, instance));
this.injector._setInstance(key, new _InProgress(future)); this.injector._setInstance(key, new _Waiting(future));
return future; return future;
} }
_resolveDependencies(key:Key, binding:Binding):List { _resolveDependencies(key:Key, binding:Binding):List {
try {
var getDependency = d => this.injector._getByKey(d.key, true, d.lazy); var getDependency = d => this.injector._getByKey(d.key, true, d.lazy);
return ListWrapper.map(binding.dependencies, getDependency); return ListWrapper.map(binding.dependencies, getDependency);
} catch (e) {
this.injector._setInstance(key, null);
if (e instanceof ProviderError) e.addKey(key);
throw e;
}
} }
_findOrCreate(key:Key, binding: Binding, deps:List) { _findOrCreate(key:Key, binding: Binding, deps:List) {
var instance = this.injector._getInstance(key); var instance = this.injector._getInstance(key);
if (! _isInProgress(instance)) return instance; if (! _isWaiting(instance)) return instance;
return binding.factory(deps); return binding.factory(deps);
} }

View File

@ -150,6 +150,9 @@ export function main() {
expect(() => injector.get(Car)) expect(() => injector.get(Car))
.toThrowError('Cannot instantiate cyclic dependency! (Car -> Engine -> Car)'); .toThrowError('Cannot instantiate cyclic dependency! (Car -> Engine -> Car)');
expect(() => injector.asyncGet(Car))
.toThrowError('Cannot instantiate cyclic dependency! (Car -> Engine -> Car)');
}); });
it('should show the full path when error happens in a constructor', function() { it('should show the full path when error happens in a constructor', function() {