@@ -5,6 +5,9 @@ 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
+ import { MissingInitializedPromiseError } from './error/MissingInitializedPromiseError' ;
10
+
8
11
9
12
/**
10
13
* TypeDI can have multiple containers.
@@ -115,6 +118,74 @@ export class ContainerInstance {
115
118
return this . getServiceValue ( identifier , service ) ;
116
119
}
117
120
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
+
118
189
/**
119
190
* Gets all instances registered in the container of the given service identifier.
120
191
* Used when service defined with multiple: true flag.
@@ -344,6 +415,100 @@ export class ContainerInstance {
344
415
return value ;
345
416
}
346
417
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
+
347
512
/**
348
513
* Initializes all parameter types for a given target service class.
349
514
*/
@@ -360,6 +525,22 @@ export class ContainerInstance {
360
525
} ) ;
361
526
}
362
527
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
+
363
544
/**
364
545
* Checks if given type is primitive (e.g. string, boolean, number, object).
365
546
*/
0 commit comments