@@ -5,6 +5,8 @@ import { Token } from './token.class';
55import { Constructable } from './types/constructable.type' ;
66import { ServiceIdentifier } from './types/service-identifier.type' ;
77import { ServiceMetadata } from './interfaces/service-metadata.interface.' ;
8+ import { AsyncInitializedService } from './types/AsyncInitializedService' ;
9+
810
911/**
1012 * TypeDI can have multiple containers.
@@ -115,6 +117,74 @@ export class ContainerInstance {
115117 return this . getServiceValue ( identifier , service ) ;
116118 }
117119
120+ /**
121+ * Like get, but returns a promise of a service that recursively resolves async properties.
122+ * Used when service defined with asyncInitialization: true flag.
123+ */
124+ getAsync < T > ( type : ObjectType < T > ) : Promise < T > ;
125+
126+ /**
127+ * Like get, but returns a promise of a service that recursively resolves async properties.
128+ * Used when service defined with asyncInitialization: true flag.
129+ */
130+ getAsync < T > ( id : string ) : Promise < T > ;
131+
132+ /**
133+ * Like get, but returns a promise of a service that recursively resolves async properties.
134+ * Used when service defined with asyncInitialization: true flag.
135+ */
136+ getAsync < T > ( id : Token < T > ) : Promise < T > ;
137+
138+ /**
139+ * Like get, but returns a promise of a service that recursively resolves async properties.
140+ * Used when service defined with asyncInitialization: true flag.
141+ */
142+ getAsync < T > ( id : { service : T } ) : Promise < T > ;
143+
144+ /**
145+ * Like get, but returns a promise of a service that recursively resolves async properties.
146+ * Used when service defined with asyncInitialization: true flag.
147+ */
148+ getAsync < T > ( identifier : ServiceIdentifier < T > ) : Promise < T > {
149+ const globalContainer = Container . of ( undefined ) ;
150+ let service = globalContainer . findService ( identifier ) ;
151+ let scopedService = this . findService ( identifier ) ;
152+
153+ if ( service && service . global === true ) return this . getServiceValueAsync ( identifier , service ) ;
154+
155+ if ( scopedService ) return this . getServiceValueAsync ( identifier , scopedService ) ;
156+
157+ if ( service && this !== globalContainer ) {
158+ const clonedService = Object . assign ( { } , service ) ;
159+ clonedService . value = undefined ;
160+ const value = this . getServiceValueAsync ( identifier , clonedService ) ;
161+ this . set ( identifier , value ) ;
162+ return value ;
163+ }
164+
165+ return this . getServiceValueAsync ( identifier , service ) ;
166+ }
167+
168+ /**
169+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
170+ * Used when services defined with multiple: true and asyncInitialization: true flags.
171+ */
172+ getManyAsync < T > ( id : string ) : T [ ] ;
173+
174+ /**
175+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
176+ * Used when services defined with multiple: true and asyncInitialization: true flags.
177+ */
178+ getManyAsync < T > ( id : Token < T > ) : T [ ] ;
179+
180+ /**
181+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
182+ * Used when services defined with multiple: true and asyncInitialization: true flags.
183+ */
184+ getManyAsync < T > ( id : string | Token < T > ) : Promise < T > [ ] {
185+ return this . filterServices ( id ) . map ( service => this . getServiceValueAsync ( id , service ) ) ;
186+ }
187+
118188 /**
119189 * Gets all instances registered in the container of the given service identifier.
120190 * Used when service defined with multiple: true flag.
@@ -344,6 +414,95 @@ export class ContainerInstance {
344414 return value ;
345415 }
346416
417+ /**
418+ * Gets a promise of an initialized AsyncService value.
419+ */
420+ private async getServiceValueAsync (
421+ identifier : ServiceIdentifier ,
422+ service : ServiceMetadata < any , any > | undefined
423+ ) : Promise < any > {
424+ // find if instance of this object already initialized in the container and return it if it is
425+ if ( service && service . value !== undefined ) return service . value ;
426+
427+ // if named service was requested and its instance was not found plus there is not type to know what to initialize,
428+ // this means service was not pre-registered and we throw an exception
429+ if (
430+ ( ! service || ! service . type ) &&
431+ ( ! service || ! service . factory ) &&
432+ ( typeof identifier === 'string' || identifier instanceof Token )
433+ )
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+ } else if ( service && service . id instanceof Function ) {
441+ type = service . id ;
442+ } else if ( identifier instanceof Function ) {
443+ type = identifier ;
444+
445+ // } else if (identifier instanceof Object && (identifier as { service: Token<any> }).service instanceof Token) {
446+ // type = (identifier as { service: Token<any> }).service;
447+ }
448+
449+ // if service was not found then create a new one and register it
450+ if ( ! service ) {
451+ if ( ! type ) throw new MissingProvidedServiceTypeError ( identifier ) ;
452+
453+ service = { type : type } ;
454+ this . services . push ( service ) ;
455+ }
456+
457+ // setup constructor parameters for a newly initialized service
458+ const paramTypes =
459+ type && Reflect && ( Reflect as any ) . getMetadata
460+ ? ( Reflect as any ) . getMetadata ( 'design:paramtypes' , type )
461+ : 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+ // filter out non-service parameters from created service constructor
468+ // non-service parameters can be, lets say Car(name: string, isNew: boolean, engine: Engine)
469+ // where name and isNew are non-service parameters and engine is a service parameter
470+ params = params . filter ( param => param !== undefined ) ;
471+
472+ if ( service . factory instanceof Array ) {
473+ // use special [Type, "create"] syntax to allow factory services
474+ // in this case Type instance will be obtained from Container and its method "create" will be called
475+ value = ( ( await this . getAsync ( service . factory [ 0 ] ) ) as any ) [ service . factory [ 1 ] ] ( ...params ) ;
476+ } else {
477+ // regular factory function
478+ value = service . factory ( ...params , this ) ;
479+ }
480+ } else {
481+ // otherwise simply create a new object instance
482+ if ( ! type ) throw new MissingProvidedServiceTypeError ( identifier ) ;
483+
484+ params . unshift ( null ) ;
485+
486+ // "extra feature" - always pass container instance as the last argument to the service function
487+ // this allows us to support javascript where we don't have decorators and emitted metadata about dependencies
488+ // need to be injected, and user can use provided container to get instances he needs
489+ params . push ( this ) ;
490+
491+ value = new ( type . bind . apply ( type , params ) ) ( ) ;
492+ }
493+
494+ if ( service && ! service . transient && value ) service . value = value ;
495+
496+ if ( type ) this . applyPropertyHandlers ( type , value ) ;
497+
498+ if ( value instanceof AsyncInitializedService ) {
499+ return new Promise ( resolve => {
500+ value . _initialized . then ( ( ) => resolve ( value ) ) ;
501+ } ) ;
502+ }
503+ return Promise . resolve ( value ) ;
504+ }
505+
347506 /**
348507 * Initializes all parameter types for a given target service class.
349508 */
@@ -360,6 +519,22 @@ export class ContainerInstance {
360519 } ) ;
361520 }
362521
522+ /**
523+ * Returns array of promises for all initialized parameter types for a given target service class.
524+ */
525+ private initializeParamsAsync ( type : Function , paramTypes : any [ ] ) : Array < Promise < any > | undefined > {
526+ return paramTypes . map ( ( paramType , index ) => {
527+ const paramHandler = Container . handlers . find ( handler => handler . object === type && handler . index === index ) ;
528+ if ( paramHandler ) return Promise . resolve ( paramHandler . value ( this ) ) ;
529+
530+ if ( paramType && paramType . name && ! this . isTypePrimitive ( paramType . name ) ) {
531+ return this . getAsync ( paramType ) ;
532+ }
533+
534+ return undefined ;
535+ } ) ;
536+ }
537+
363538 /**
364539 * Checks if given type is primitive (e.g. string, boolean, number, object).
365540 */
0 commit comments