@@ -5,6 +5,9 @@ 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+ import { MissingInitializedPromiseError } from './error/MissingInitializedPromiseError' ;
10+
811
912/**
1013 * TypeDI can have multiple containers.
@@ -115,6 +118,74 @@ export class ContainerInstance {
115118 return this . getServiceValue ( identifier , service ) ;
116119 }
117120
121+ /**
122+ * Like get, but returns a promise of a service that recursively resolves async properties.
123+ * Used when service defined with asyncInitialization: true flag.
124+ */
125+ getAsync < T > ( type : ObjectType < T > ) : Promise < T > ;
126+
127+ /**
128+ * Like get, but returns a promise of a service that recursively resolves async properties.
129+ * Used when service defined with asyncInitialization: true flag.
130+ */
131+ getAsync < T > ( id : string ) : Promise < T > ;
132+
133+ /**
134+ * Like get, but returns a promise of a service that recursively resolves async properties.
135+ * Used when service defined with asyncInitialization: true flag.
136+ */
137+ getAsync < T > ( id : Token < T > ) : Promise < T > ;
138+
139+ /**
140+ * Like get, but returns a promise of a service that recursively resolves async properties.
141+ * Used when service defined with asyncInitialization: true flag.
142+ */
143+ getAsync < T > ( id : { service : T } ) : Promise < T > ;
144+
145+ /**
146+ * Like get, but returns a promise of a service that recursively resolves async properties.
147+ * Used when service defined with asyncInitialization: true flag.
148+ */
149+ getAsync < T > ( identifier : ServiceIdentifier < T > ) : Promise < T > {
150+ const globalContainer = Container . of ( undefined ) ;
151+ const service = globalContainer . findService ( identifier ) ;
152+ const scopedService = this . findService ( identifier ) ;
153+
154+ if ( service && service . global === true ) return this . getServiceValueAsync ( identifier , service ) ;
155+
156+ if ( scopedService ) return this . getServiceValueAsync ( identifier , scopedService ) ;
157+
158+ if ( service && this !== globalContainer ) {
159+ const clonedService = Object . assign ( { } , service ) ;
160+ clonedService . value = undefined ;
161+ const value = this . getServiceValueAsync ( identifier , clonedService ) ;
162+ this . set ( identifier , value ) ;
163+ return value ;
164+ }
165+
166+ return this . getServiceValueAsync ( identifier , service ) ;
167+ }
168+
169+ /**
170+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
171+ * Used when services defined with multiple: true and asyncInitialization: true flags.
172+ */
173+ getManyAsync < T > ( id : string ) : T [ ] ;
174+
175+ /**
176+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
177+ * Used when services defined with multiple: true and asyncInitialization: true flags.
178+ */
179+ getManyAsync < T > ( id : Token < T > ) : T [ ] ;
180+
181+ /**
182+ * Like getMany, but returns a promise that recursively resolves async properties on all services.
183+ * Used when services defined with multiple: true and asyncInitialization: true flags.
184+ */
185+ getManyAsync < T > ( id : string | Token < T > ) : Promise < T > [ ] {
186+ return this . filterServices ( id ) . map ( service => this . getServiceValueAsync ( id , service ) ) ;
187+ }
188+
118189 /**
119190 * Gets all instances registered in the container of the given service identifier.
120191 * Used when service defined with multiple: true flag.
@@ -344,6 +415,100 @@ export class ContainerInstance {
344415 return value ;
345416 }
346417
418+ /**
419+ * Gets a promise of an initialized AsyncService value.
420+ */
421+ private async getServiceValueAsync (
422+ identifier : ServiceIdentifier ,
423+ service : ServiceMetadata < any , any > | undefined
424+ ) : Promise < any > {
425+ // find if instance of this object already initialized in the container and return it if it is
426+ if ( service && service . value !== undefined ) return service . value ;
427+
428+ // if named service was requested and its instance was not found plus there is not type to know what to initialize,
429+ // this means service was not pre-registered and we throw an exception
430+ if (
431+ ( ! service || ! service . type ) &&
432+ ( ! service || ! service . factory ) &&
433+ ( typeof identifier === 'string' || identifier instanceof Token )
434+ )
435+ throw new ServiceNotFoundError ( identifier ) ;
436+
437+ // at this point we either have type in service registered, either identifier is a target type
438+ let type = undefined ;
439+ if ( service && service . type ) {
440+ type = service . type ;
441+ } else if ( service && service . id instanceof Function ) {
442+ type = service . id ;
443+ } else if ( identifier instanceof Function ) {
444+ type = identifier ;
445+
446+ // } else if (identifier instanceof Object && (identifier as { service: Token<any> }).service instanceof Token) {
447+ // type = (identifier as { service: Token<any> }).service;
448+ }
449+
450+ // if service was not found then create a new one and register it
451+ if ( ! service ) {
452+ if ( ! type ) throw new MissingProvidedServiceTypeError ( identifier ) ;
453+
454+ service = { type : type } ;
455+ this . services . push ( service ) ;
456+ }
457+
458+ // setup constructor parameters for a newly initialized service
459+ const paramTypes =
460+ type && Reflect && ( Reflect as any ) . getMetadata
461+ ? ( Reflect as any ) . getMetadata ( 'design:paramtypes' , type )
462+ : undefined ;
463+ let params : any [ ] = paramTypes ? await Promise . all ( this . initializeParamsAsync ( type , paramTypes ) ) : [ ] ;
464+
465+ // if factory is set then use it to create service instance
466+ let value : any ;
467+ if ( service . factory ) {
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 ] ) ) [ service . factory [ 1 ] ] ( ...params ) ;
477+ } else {
478+ // regular factory function
479+ value = service . factory ( ...params , this ) ;
480+ }
481+ } else {
482+ // otherwise simply create a new object instance
483+ if ( ! type ) throw new MissingProvidedServiceTypeError ( identifier ) ;
484+
485+ params . unshift ( null ) ;
486+
487+ // "extra feature" - always pass container instance as the last argument to the service function
488+ // this allows us to support javascript where we don't have decorators and emitted metadata about dependencies
489+ // need to be injected, and user can use provided container to get instances he needs
490+ params . push ( this ) ;
491+
492+ // eslint-disable-next-line prefer-spread
493+ value = new ( type . bind . apply ( type , params ) ) ( ) ;
494+ }
495+
496+ if ( service && ! service . transient && value ) service . value = value ;
497+
498+ if ( type ) this . applyPropertyHandlers ( type , value ) ;
499+
500+ if ( value instanceof AsyncInitializedService || service . asyncInitialization ) {
501+ return new Promise ( ( resolve ) => {
502+ if ( ! ( value . initialized instanceof Promise ) && service . asyncInitialization ) {
503+ throw new MissingInitializedPromiseError ( service . value ) ;
504+ }
505+
506+ value . initialized . then ( ( ) => resolve ( value ) ) ;
507+ } ) ;
508+ }
509+ return Promise . resolve ( value ) ;
510+ }
511+
347512 /**
348513 * Initializes all parameter types for a given target service class.
349514 */
@@ -360,6 +525,22 @@ export class ContainerInstance {
360525 } ) ;
361526 }
362527
528+ /**
529+ * Returns array of promises for all initialized parameter types for a given target service class.
530+ */
531+ private initializeParamsAsync ( type : Function , paramTypes : any [ ] ) : Array < Promise < any > | undefined > {
532+ return paramTypes . map ( ( paramType , index ) => {
533+ const paramHandler = Container . handlers . find ( handler => handler . object === type && handler . index === index ) ;
534+ if ( paramHandler ) 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+
363544 /**
364545 * Checks if given type is primitive (e.g. string, boolean, number, object).
365546 */
0 commit comments