@@ -5,6 +5,7 @@ import {Token} from "./Token";
5
5
import { ObjectType } from "./types/ObjectType" ;
6
6
import { ServiceIdentifier } from "./types/ServiceIdentifier" ;
7
7
import { ServiceMetadata } from "./types/ServiceMetadata" ;
8
+ import { AsyncInitializedService } from "./types/AsyncInitializedService" ;
8
9
9
10
/**
10
11
* TypeDI can have multiple containers.
@@ -139,6 +140,77 @@ export class ContainerInstance {
139
140
return this . filterServices ( id ) . map ( service => this . getServiceValue ( id , service ) ) ;
140
141
}
141
142
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
+
142
214
/**
143
215
* Sets a value for the given type or service name in the container.
144
216
*/
@@ -345,6 +417,96 @@ export class ContainerInstance {
345
417
return value ;
346
418
}
347
419
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
+
348
510
/**
349
511
* Initializes all parameter types for a given target service class.
350
512
*/
@@ -362,6 +524,23 @@ export class ContainerInstance {
362
524
} ) ;
363
525
}
364
526
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
+
365
544
/**
366
545
* Checks if given type is primitive (e.g. string, boolean, number, object).
367
546
*/
0 commit comments