@@ -5,6 +5,7 @@ import {Token} from "./Token";
55import { ObjectType } from "./types/ObjectType" ;
66import { ServiceIdentifier } from "./types/ServiceIdentifier" ;
77import { ServiceMetadata } from "./types/ServiceMetadata" ;
8+ import { AsyncInitializedService } from "./types/AsyncInitializedService" ;
89
910/**
1011 * TypeDI can have multiple containers.
@@ -139,6 +140,77 @@ export class ContainerInstance {
139140 return this . filterServices ( id ) . map ( service => this . getServiceValue ( id , service ) ) ;
140141 }
141142
143+ /**
144+ * Like get, but returns a promise of a service that recursively resolves async properties.
145+ * Used when service defined with asyncInitialization: true flag.
146+ */
147+ getAsync < T > ( type : ObjectType < T > ) : Promise < T > ;
148+
149+ /**
150+ * Like get, but returns a promise of a service that recursively resolves async properties.
151+ * Used when service defined with asyncInitialization: true flag.
152+ */
153+ getAsync < T > ( id : string ) : Promise < T > ;
154+
155+ /**
156+ * Like get, but returns a promise of a service that recursively resolves async properties.
157+ * Used when service defined with asyncInitialization: true flag.
158+ */
159+ getAsync < T > ( id : Token < T > ) : Promise < T > ;
160+
161+ /**
162+ * Like get, but returns a promise of a service that recursively resolves async properties.
163+ * Used when service defined with asyncInitialization: true flag.
164+ */
165+ getAsync < T > ( id : { service : T } ) : Promise < T > ;
166+
167+ /**
168+ * Like get, but returns a promise of a service that recursively resolves async properties.
169+ * Used when service defined with asyncInitialization: true flag.
170+ */
171+ getAsync < T > ( identifier : ServiceIdentifier < T > ) : Promise < T > {
172+
173+ const globalContainer = Container . of ( undefined ) ;
174+ let service = globalContainer . findService ( identifier ) ;
175+ let scopedService = this . findService ( identifier ) ;
176+
177+ if ( service && service . global === true )
178+ return this . getServiceValueAsync ( identifier , service ) ;
179+
180+ if ( scopedService )
181+ return this . getServiceValueAsync ( identifier , scopedService ) ;
182+
183+ if ( service && this !== globalContainer ) {
184+ const clonedService = Object . assign ( { } , service ) ;
185+ clonedService . value = undefined ;
186+ const value = this . getServiceValueAsync ( identifier , clonedService ) ;
187+ this . set ( identifier , value ) ;
188+ return value ;
189+ }
190+
191+ return this . getServiceValueAsync ( identifier , service ) ;
192+ }
193+
194+ /**
195+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
196+ * Used when services defined with multiple: true and asyncInitialization: true flags.
197+ */
198+ getManyAsync < T > ( id : string ) : T [ ] ;
199+
200+ /**
201+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
202+ * Used when services defined with multiple: true and asyncInitialization: true flags.
203+ */
204+ getManyAsync < T > ( id : Token < T > ) : T [ ] ;
205+
206+ /**
207+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
208+ * Used when services defined with multiple: true and asyncInitialization: true flags.
209+ */
210+ getManyAsync < T > ( id : string | Token < T > ) : Promise < T > [ ] {
211+ return this . filterServices ( id ) . map ( service => this . getServiceValueAsync ( id , service ) ) ;
212+ }
213+
142214 /**
143215 * Sets a value for the given type or service name in the container.
144216 */
@@ -345,6 +417,96 @@ export class ContainerInstance {
345417 return value ;
346418 }
347419
420+ /**
421+ * Gets a promise of an initialized AsyncService value.
422+ */
423+ private async getServiceValueAsync ( identifier : ServiceIdentifier , service : ServiceMetadata < any , any > | undefined ) : Promise < any > {
424+
425+ // find if instance of this object already initialized in the container and return it if it is
426+ if ( service && service . value !== undefined )
427+ return service . value ;
428+
429+ // if named service was requested and its instance was not found plus there is not type to know what to initialize,
430+ // this means service was not pre-registered and we throw an exception
431+ if ( ( ! service || ! service . type ) &&
432+ ( ! service || ! service . factory ) &&
433+ ( typeof identifier === "string" || identifier instanceof Token ) )
434+ throw new ServiceNotFoundError ( identifier ) ;
435+
436+ // at this point we either have type in service registered, either identifier is a target type
437+ let type = undefined ;
438+ if ( service && service . type ) {
439+ type = service . type ;
440+
441+ } else if ( service && service . id instanceof Function ) {
442+ type = service . id ;
443+
444+ } else if ( identifier instanceof Function ) {
445+ type = identifier ;
446+
447+ // } else if (identifier instanceof Object && (identifier as { service: Token<any> }).service instanceof Token) {
448+ // type = (identifier as { service: Token<any> }).service;
449+ }
450+
451+ // if service was not found then create a new one and register it
452+ if ( ! service ) {
453+ if ( ! type )
454+ throw new MissingProvidedServiceTypeError ( identifier ) ;
455+
456+ service = { type : type } ;
457+ this . services . push ( service ) ;
458+ }
459+
460+ // setup constructor parameters for a newly initialized service
461+ const paramTypes = type && Reflect && ( Reflect as any ) . getMetadata ? ( Reflect as any ) . getMetadata ( "design:paramtypes" , type ) : undefined ;
462+ let params : any [ ] = paramTypes ? await Promise . all ( this . initializeParamsAsync ( type , paramTypes ) ) : [ ] ;
463+
464+ // if factory is set then use it to create service instance
465+ let value : any ;
466+ if ( service . factory ) {
467+
468+ // filter out non-service parameters from created service constructor
469+ // non-service parameters can be, lets say Car(name: string, isNew: boolean, engine: Engine)
470+ // where name and isNew are non-service parameters and engine is a service parameter
471+ params = params . filter ( param => param !== undefined ) ;
472+
473+ if ( service . factory instanceof Array ) {
474+ // use special [Type, "create"] syntax to allow factory services
475+ // in this case Type instance will be obtained from Container and its method "create" will be called
476+ value = ( await this . getAsync ( service . factory [ 0 ] ) as any ) [ service . factory [ 1 ] ] ( ...params ) ;
477+
478+ } else { // regular factory function
479+ value = service . factory ( ...params , this ) ;
480+ }
481+
482+ } else { // otherwise simply create a new object instance
483+ if ( ! type )
484+ throw new MissingProvidedServiceTypeError ( identifier ) ;
485+
486+ params . unshift ( null ) ;
487+
488+ // "extra feature" - always pass container instance as the last argument to the service function
489+ // this allows us to support javascript where we don't have decorators and emitted metadata about dependencies
490+ // need to be injected, and user can use provided container to get instances he needs
491+ params . push ( this ) ;
492+
493+ value = new ( type . bind . apply ( type , params ) ) ( ) ;
494+ }
495+
496+ if ( service && ! service . transient && value )
497+ service . value = value ;
498+
499+ if ( type )
500+ this . applyPropertyHandlers ( type , value ) ;
501+
502+ if ( value instanceof AsyncInitializedService ) {
503+ return new Promise ( ( resolve ) => {
504+ value . _initialized . then ( ( ) => resolve ( value ) ) ;
505+ } ) ;
506+ }
507+ return Promise . resolve ( value ) ;
508+ }
509+
348510 /**
349511 * Initializes all parameter types for a given target service class.
350512 */
@@ -362,6 +524,23 @@ export class ContainerInstance {
362524 } ) ;
363525 }
364526
527+ /**
528+ * Returns array of promises for all initialized parameter types for a given target service class.
529+ */
530+ private initializeParamsAsync ( type : Function , paramTypes : any [ ] ) : Array < Promise < any > | undefined > {
531+ return paramTypes . map ( ( paramType , index ) => {
532+ const paramHandler = Container . handlers . find ( handler => handler . object === type && handler . index === index ) ;
533+ if ( paramHandler )
534+ return Promise . resolve ( paramHandler . value ( this ) ) ;
535+
536+ if ( paramType && paramType . name && ! this . isTypePrimitive ( paramType . name ) ) {
537+ return this . getAsync ( paramType ) ;
538+ }
539+
540+ return undefined ;
541+ } ) ;
542+ }
543+
365544 /**
366545 * Checks if given type is primitive (e.g. string, boolean, number, object).
367546 */
0 commit comments