From e7666d06120d7afb0322e6f762d41cd17c609c1d Mon Sep 17 00:00:00 2001 From: vsavkin Date: Mon, 6 Oct 2014 16:24:12 -0400 Subject: [PATCH] feat(injector): handle async cyclic dependencies --- modules/di/src/injector.js | 39 +++++++++++++++++++---------- modules/di/test/di/injector_spec.js | 3 +++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/modules/di/src/injector.js b/modules/di/src/injector.js index 6536516269..f8529560a6 100644 --- a/modules/di/src/injector.js +++ b/modules/di/src/injector.js @@ -6,14 +6,15 @@ import {Type, isPresent, isBlank} from 'facade/lang'; import {Future, FutureWrapper} from 'facade/async'; import {Key} from './key'; -class _InProgress { +var _constructing = new Object(); + +class _Waiting { constructor(future:Future) { this.future = future; } } -var _isInProgressSync = new _InProgress(null); -function _isInProgress(obj) { - return obj instanceof _InProgress; +function _isWaiting(obj) { + return obj instanceof _Waiting; } @@ -108,10 +109,9 @@ class _SyncInjectorStrategy { var instance = this.injector._getInstance(key); - // skip async in-progress - if (instance === _isInProgressSync) { + if (instance === _constructing) { throw new CyclicDependencyError(key); - } else if (isPresent(instance) && !_isInProgress(instance)) { + } else if (isPresent(instance) && !_isWaiting(instance)) { return instance; } else { return null; @@ -125,7 +125,7 @@ class _SyncInjectorStrategy { if (binding.providedAsFuture) throw new AsyncBindingError(key); //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); return this._createInstance(key, binding, deps); @@ -136,6 +136,7 @@ class _SyncInjectorStrategy { var getDependency = d => this.injector._getByKey(d.key, d.asFuture, d.lazy); return ListWrapper.map(binding.dependencies, getDependency); } catch (e) { + this.injector._setInstance(key, null); if (e instanceof ProviderError) e.addKey(key); throw e; } @@ -165,7 +166,10 @@ class _AsyncInjectorStrategy { } var instance = this.injector._getInstance(key); - if (_isInProgress(instance)) { + + if (instance === _constructing) { + throw new CyclicDependencyError(key); + } else if (_isWaiting(instance)) { return instance.future; } else if (isPresent(instance)) { return FutureWrapper.value(instance); @@ -178,23 +182,32 @@ class _AsyncInjectorStrategy { var binding = this.injector._getBinding(key); 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 future = FutureWrapper.wait(deps). then(deps => this._findOrCreate(key, binding, deps)). then(instance => this._cacheInstance(key, instance)); - this.injector._setInstance(key, new _InProgress(future)); + this.injector._setInstance(key, new _Waiting(future)); return future; } _resolveDependencies(key:Key, binding:Binding):List { - var getDependency = d => this.injector._getByKey(d.key, true, d.lazy); - return ListWrapper.map(binding.dependencies, getDependency); + try { + var getDependency = d => this.injector._getByKey(d.key, true, d.lazy); + 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) { var instance = this.injector._getInstance(key); - if (! _isInProgress(instance)) return instance; + if (! _isWaiting(instance)) return instance; return binding.factory(deps); } diff --git a/modules/di/test/di/injector_spec.js b/modules/di/test/di/injector_spec.js index 3eb9b8b3f8..b997cd3199 100644 --- a/modules/di/test/di/injector_spec.js +++ b/modules/di/test/di/injector_spec.js @@ -150,6 +150,9 @@ export function main() { expect(() => injector.get(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() {