Skip to content

Commit 23851ba

Browse files
Julien HellerNoNameProvided
authored andcommitted
Add getAsync and getManyAsync to Container and ContainerInstance
1 parent faa98b0 commit 23851ba

File tree

2 files changed

+228
-0
lines changed

2 files changed

+228
-0
lines changed

src/container-instance.class.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { Token } from './token.class';
55
import { Constructable } from './types/constructable.type';
66
import { ServiceIdentifier } from './types/service-identifier.type';
77
import { 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
*/

src/container.class.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Constructable } from './types/constructable.type';
55
import { ServiceIdentifier } from './types/service-identifier.type';
66
import { ServiceMetadata } from './interfaces/service-metadata.interface.';
77

8+
89
/**
910
* Service container.
1011
*/
@@ -105,6 +106,38 @@ export class Container {
105106
return this.globalInstance.get(identifier as any);
106107
}
107108

109+
/**
110+
* Like get, but returns a promise of a service that recursively resolves async properties.
111+
* Used when service defined with asyncInitialization: true flag.
112+
*/
113+
static getAsync<T>(type: ObjectType<T>): Promise<T>;
114+
115+
/**
116+
* Like get, but returns a promise of a service that recursively resolves async properties.
117+
* Used when service defined with asyncInitialization: true flag.
118+
*/
119+
static getAsync<T>(id: string): Promise<T>;
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+
static getAsync<T>(id: Token<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+
static getAsync<T>(service: { service: T }): 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+
static getAsync<T>(identifier: ServiceIdentifier<T>): Promise<T> {
138+
return this.globalInstance.getAsync(identifier as any);
139+
}
140+
108141
/**
109142
* Gets all instances registered in the container of the given service identifier.
110143
* Used when service defined with multiple: true flag.
@@ -125,6 +158,26 @@ export class Container {
125158
return this.globalInstance.getMany(id as any);
126159
}
127160

161+
/**
162+
* Like getMany, but returns a promise that recursively resolves async properties on all services.
163+
* Used when services defined with multiple: true and asyncInitialization: true flags.
164+
*/
165+
static getManyAsync<T>(id: string): T[];
166+
167+
/**
168+
* Like getMany, but returns a promise that recursively resolves async properties on all services.
169+
* Used when services defined with multiple: true and asyncInitialization: true flags.
170+
*/
171+
static getManyAsync<T>(id: Token<T>): T[];
172+
173+
/**
174+
* Like getMany, but returns a promise that recursively resolves async properties on all services.
175+
* Used when services defined with multiple: true and asyncInitialization: true flags.
176+
*/
177+
static getManyAsync<T>(id: string | Token<T>): Promise<T>[] {
178+
return this.globalInstance.getManyAsync(id as any);
179+
}
180+
128181
/**
129182
* Sets a value for the given type or service name in the container.
130183
*/

0 commit comments

Comments
 (0)