Skip to content

Commit 4e0b14a

Browse files
author
Julien Heller
committed
Add getAsync and getManyAsync to Container and ContainerInstance
1 parent 761c5ad commit 4e0b14a

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

src/Container.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {Handler} from "./types/Handler";
44
import {ObjectType} from "./types/ObjectType";
55
import {ServiceIdentifier} from "./types/ServiceIdentifier";
66
import {ServiceMetadata} from "./types/ServiceMetadata";
7+
import {AsyncInitializedService} from "./types/AsyncInitializedService";
78

89
/**
910
* Service container.
@@ -107,6 +108,38 @@ export class Container {
107108
return this.globalInstance.get(identifier as any);
108109
}
109110

111+
/**
112+
* Like get, but returns a promise of a service that recursively resolves async properties.
113+
* Used when service defined with asyncInitialization: true flag.
114+
*/
115+
static getAsync<T>(type: ObjectType<T>): Promise<T>;
116+
117+
/**
118+
* Like get, but returns a promise of a service that recursively resolves async properties.
119+
* Used when service defined with asyncInitialization: true flag.
120+
*/
121+
static getAsync<T>(id: string): Promise<T>;
122+
123+
/**
124+
* Like get, but returns a promise of a service that recursively resolves async properties.
125+
* Used when service defined with asyncInitialization: true flag.
126+
*/
127+
static getAsync<T>(id: Token<T>): Promise<T>;
128+
129+
/**
130+
* Like get, but returns a promise of a service that recursively resolves async properties.
131+
* Used when service defined with asyncInitialization: true flag.
132+
*/
133+
static getAsync<T>(service: { service: T }): Promise<T>;
134+
135+
/**
136+
* Like get, but returns a promise of a service that recursively resolves async properties.
137+
* Used when service defined with asyncInitialization: true flag.
138+
*/
139+
static getAsync<T>(identifier: ServiceIdentifier<T>): Promise<T> {
140+
return this.globalInstance.getAsync(identifier as any);
141+
}
142+
110143
/**
111144
* Gets all instances registered in the container of the given service identifier.
112145
* Used when service defined with multiple: true flag.
@@ -127,6 +160,26 @@ export class Container {
127160
return this.globalInstance.getMany(id as any);
128161
}
129162

163+
/**
164+
* Like getMany, but returns a promise that recursively resolves async properties on all services.
165+
* Used when services defined with multiple: true and asyncInitialization: true flags.
166+
*/
167+
static getManyAsync<T>(id: string): T[];
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+
static getManyAsync<T>(id: Token<T>): 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+
static getManyAsync<T>(id: string|Token<T>): Promise<T>[] {
180+
return this.globalInstance.getManyAsync(id as any);
181+
}
182+
130183
/**
131184
* Sets a value for the given type or service name in the container.
132185
*/

src/ContainerInstance.ts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {Token} from "./Token";
55
import {ObjectType} from "./types/ObjectType";
66
import {ServiceIdentifier} from "./types/ServiceIdentifier";
77
import {ServiceMetadata} from "./types/ServiceMetadata";
8+
import {AsyncInitializedService} from "./types/AsyncInitializedService";
89

910
/**
1011
* TypeDI can have multiple containers.
@@ -139,6 +140,77 @@ export class ContainerInstance {
139140
return this.filterServices(id).map(service => this.getServiceValue(id, service));
140141
}
141142

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+
142214
/**
143215
* Sets a value for the given type or service name in the container.
144216
*/
@@ -345,6 +417,96 @@ export class ContainerInstance {
345417
return value;
346418
}
347419

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+
348510
/**
349511
* Initializes all parameter types for a given target service class.
350512
*/
@@ -362,6 +524,23 @@ export class ContainerInstance {
362524
});
363525
}
364526

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+
365544
/**
366545
* Checks if given type is primitive (e.g. string, boolean, number, object).
367546
*/

0 commit comments

Comments
 (0)