@@ -5,6 +5,8 @@ import { Token } from './token.class';
5
5
import { Constructable } from './types/constructable.type' ;
6
6
import { ServiceIdentifier } from './types/service-identifier.type' ;
7
7
import { ServiceMetadata } from './interfaces/service-metadata.interface.' ;
8
+ import { AsyncInitializedService } from './types/AsyncInitializedService' ;
9
+
8
10
9
11
/**
10
12
* TypeDI can have multiple containers.
@@ -115,6 +117,74 @@ export class ContainerInstance {
115
117
return this . getServiceValue ( identifier , service ) ;
116
118
}
117
119
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
+
118
188
/**
119
189
* Gets all instances registered in the container of the given service identifier.
120
190
* Used when service defined with multiple: true flag.
@@ -344,6 +414,95 @@ export class ContainerInstance {
344
414
return value ;
345
415
}
346
416
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
+
347
506
/**
348
507
* Initializes all parameter types for a given target service class.
349
508
*/
@@ -360,6 +519,22 @@ export class ContainerInstance {
360
519
} ) ;
361
520
}
362
521
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
+
363
538
/**
364
539
* Checks if given type is primitive (e.g. string, boolean, number, object).
365
540
*/
0 commit comments