Skip to content

Commit f16295a

Browse files
committed
Add explicit instantation mode
1 parent e8e1aca commit f16295a

File tree

3 files changed

+132
-42
lines changed

3 files changed

+132
-42
lines changed

packages/component/src/provider.test.ts

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ describe('Provider', () => {
9393

9494
it('does not throw if instance factory throws when registering a component with a pending promise', () => {
9595
// create a pending promise
96-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
97-
provider.get();
96+
void provider.get();
9897
const component = getFakeComponent('test', () => {
9998
throw Error('something went wrong!');
10099
});
@@ -127,17 +126,34 @@ describe('Provider', () => {
127126

128127
expect((instance as any).options).to.deep.equal(options);
129128
});
129+
130+
it('resolve pending promises created by Provider.get() with the same identifier', () => {
131+
provider.setComponent(
132+
getFakeComponent(
133+
'test',
134+
() => ({ test: true }),
135+
false,
136+
InstantiationMode.EXPLICIT
137+
)
138+
);
139+
const servicePromise = provider.get();
140+
expect((provider as any).instances.size).to.equal(0);
141+
142+
provider.initialize();
143+
expect((provider as any).instances.size).to.equal(1);
144+
return expect(servicePromise).to.eventually.deep.equal({ test: true });
145+
});
130146
});
131147

132148
describe('Provider (multipleInstances = false)', () => {
133149
describe('getImmediate()', () => {
134-
it('throws if the service is not available', () => {
150+
it('throws if component has not been registered', () => {
135151
expect(provider.getImmediate.bind(provider)).to.throw(
136152
'Service test is not available'
137153
);
138154
});
139155

140-
it('returns null if the service is not available with optional flag', () => {
156+
it('returns null with the optional flag set if component has not been registered ', () => {
141157
expect(provider.getImmediate({ optional: true })).to.equal(null);
142158
});
143159

@@ -186,8 +202,7 @@ describe('Provider', () => {
186202
describe('setComponent()', () => {
187203
it('instantiates the service if there is a pending promise and the service is eager', () => {
188204
// create a pending promise
189-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
190-
provider.get();
205+
void provider.get();
191206

192207
provider.setComponent(
193208
getFakeComponent('test', () => ({}), false, InstantiationMode.EAGER)
@@ -197,8 +212,7 @@ describe('Provider', () => {
197212

198213
it('instantiates the service if there is a pending promise and the service is NOT eager', () => {
199214
// create a pending promise
200-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
201-
provider.get();
215+
void provider.get();
202216

203217
provider.setComponent(getFakeComponent('test', () => ({})));
204218
expect((provider as any).instances.size).to.equal(1);
@@ -251,8 +265,7 @@ describe('Provider', () => {
251265
)
252266
);
253267

254-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
255-
provider.delete();
268+
void provider.delete();
256269

257270
expect(deleteFake).to.have.been.called;
258271
});
@@ -274,8 +287,7 @@ describe('Provider', () => {
274287
)
275288
);
276289

277-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
278-
provider.delete();
290+
void provider.delete();
279291

280292
expect(deleteFake).to.have.been.called;
281293
});
@@ -350,12 +362,10 @@ describe('Provider', () => {
350362

351363
describe('setComponent()', () => {
352364
it('instantiates services for the pending promises for all instance identifiers', async () => {
353-
/* eslint-disable @typescript-eslint/no-floating-promises */
354365
// create 3 promises for 3 different identifiers
355-
provider.get();
356-
provider.get('name1');
357-
provider.get('name2');
358-
/* eslint-enable @typescript-eslint/no-floating-promises */
366+
void provider.get();
367+
void provider.get('name1');
368+
void provider.get('name2');
359369

360370
provider.setComponent(
361371
getFakeComponent('test', () => ({ test: true }), true)
@@ -378,8 +388,7 @@ describe('Provider', () => {
378388

379389
it(`instantiates the default serviec if there are pending promises for other identifiers
380390
but not for the default identifer and the service is eager`, () => {
381-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
382-
provider.get('name1');
391+
void provider.get('name1');
383392
provider.setComponent(
384393
getFakeComponent(
385394
'test',
@@ -415,8 +424,7 @@ describe('Provider', () => {
415424
provider.getImmediate({ identifier: 'instance1' });
416425
provider.getImmediate({ identifier: 'instance2' });
417426

418-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
419-
provider.delete();
427+
void provider.delete();
420428

421429
expect(deleteFakes.length).to.equal(2);
422430
for (const f of deleteFakes) {
@@ -478,4 +486,47 @@ describe('Provider', () => {
478486
});
479487
});
480488
});
489+
490+
describe('InstantiationMode: EXPLICIT', () => {
491+
it('setComponent() does NOT auto-initialize the service', () => {
492+
// create a pending promise which should trigger initialization if instantiationMode is non-EXPLICIT
493+
void provider.get();
494+
495+
provider.setComponent(
496+
getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT)
497+
);
498+
expect((provider as any).instances.size).to.equal(0);
499+
});
500+
501+
it('get() does NOT auto-initialize the service', () => {
502+
provider.setComponent(
503+
getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT)
504+
);
505+
expect((provider as any).instances.size).to.equal(0);
506+
void provider.get();
507+
expect((provider as any).instances.size).to.equal(0);
508+
});
509+
510+
it('getImmediate() does NOT auto-initialize the service and throws if the service has not been initialized', () => {
511+
provider.setComponent(
512+
getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT)
513+
);
514+
expect((provider as any).instances.size).to.equal(0);
515+
516+
expect(() => provider.getImmediate()).to.throw(
517+
'Service test is not available'
518+
);
519+
expect((provider as any).instances.size).to.equal(0);
520+
});
521+
522+
it('getImmediate() does NOT auto-initialize the service and returns null if the optional flag is set', () => {
523+
provider.setComponent(
524+
getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT)
525+
);
526+
expect((provider as any).instances.size).to.equal(0);
527+
528+
expect(provider.getImmediate({ optional: true })).to.equal(null);
529+
expect((provider as any).instances.size).to.equal(0);
530+
});
531+
});
481532
});

packages/component/src/provider.ts

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,23 @@ export class Provider<T extends Name> {
5454
if (!this.instancesDeferred.has(normalizedIdentifier)) {
5555
const deferred = new Deferred<NameServiceMapping[T]>();
5656
this.instancesDeferred.set(normalizedIdentifier, deferred);
57-
// If the service instance is available, resolve the promise with it immediately
58-
try {
59-
const instance = this.getOrInitializeService({
60-
instanceIdentifier: normalizedIdentifier
61-
});
62-
if (instance) {
63-
deferred.resolve(instance);
57+
58+
if (
59+
this.isInitialized(normalizedIdentifier) ||
60+
this.shouldAutoInitialize()
61+
) {
62+
// initialize the service if it can be auto-initialized
63+
try {
64+
const instance = this.getOrInitializeService({
65+
instanceIdentifier: normalizedIdentifier
66+
});
67+
if (instance) {
68+
deferred.resolve(instance);
69+
}
70+
} catch (e) {
71+
// when the instance factory throws an exception during get(), it should not cause
72+
// a fatal error. We just return the unresolved promise in this case.
6473
}
65-
} catch (e) {
66-
// when the instance factory throws an exception during get(), it should not cause
67-
// a fatal error. We just return the unresolved promise in this case.
6874
}
6975
}
7076

@@ -98,23 +104,28 @@ export class Provider<T extends Name> {
98104
};
99105
// if multipleInstances is not supported, use the default name
100106
const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier);
101-
try {
102-
const instance = this.getOrInitializeService({
103-
instanceIdentifier: normalizedIdentifier
104-
});
105107

106-
if (!instance) {
108+
if (
109+
this.isInitialized(normalizedIdentifier) ||
110+
this.shouldAutoInitialize()
111+
) {
112+
try {
113+
return this.getOrInitializeService({
114+
instanceIdentifier: normalizedIdentifier
115+
});
116+
} catch (e) {
107117
if (optional) {
108118
return null;
119+
} else {
120+
throw e;
109121
}
110-
throw Error(`Service ${this.name} is not available`);
111122
}
112-
return instance;
113-
} catch (e) {
123+
} else {
124+
// In case a component is not initialized and should/can not be auto-initialized at the moment, return null if the optional flag is set, or throw
114125
if (optional) {
115126
return null;
116127
} else {
117-
throw e;
128+
throw Error(`Service ${this.name} is not available`);
118129
}
119130
}
120131
}
@@ -135,6 +146,12 @@ export class Provider<T extends Name> {
135146
}
136147

137148
this.component = component;
149+
150+
// return early without attempting to initialize the component if the component requires explicit initialization (calling `Provider.initialize()`)
151+
if (!this.shouldAutoInitialize()) {
152+
return;
153+
}
154+
138155
// if the service is eager, initialize the default instance
139156
if (isComponentEager(component)) {
140157
try {
@@ -216,10 +233,24 @@ export class Provider<T extends Name> {
216233
throw Error(`Component ${this.name} has not been registered yet`);
217234
}
218235

219-
return this.getOrInitializeService({
236+
const instance = this.getOrInitializeService({
220237
instanceIdentifier: normalizedIdentifier,
221238
options
222239
})!;
240+
241+
// resolve any pending promise waiting for the service instance
242+
for (const [
243+
instanceIdentifier,
244+
instanceDeferred
245+
] of this.instancesDeferred.entries()) {
246+
const normalizedDeferredIdentifier = this.normalizeInstanceIdentifier(
247+
instanceIdentifier
248+
);
249+
if (normalizedIdentifier === normalizedDeferredIdentifier) {
250+
instanceDeferred.resolve(instance);
251+
}
252+
}
253+
return instance;
223254
}
224255

225256
private getOrInitializeService({
@@ -248,6 +279,13 @@ export class Provider<T extends Name> {
248279
return identifier; // assume multiple instances are supported before the component is provided.
249280
}
250281
}
282+
283+
private shouldAutoInitialize(): boolean {
284+
return (
285+
!!this.component &&
286+
this.component.instantiationMode !== InstantiationMode.EXPLICIT
287+
);
288+
}
251289
}
252290

253291
// undefined should be passed to the service factory for the default instance

packages/component/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { ComponentContainer } from './component_container';
1919

2020
export const enum InstantiationMode {
2121
LAZY = 'LAZY', // Currently all components are LAZY in JS SDK
22-
EAGER = 'EAGER'
22+
EAGER = 'EAGER',
23+
EXPLICIT = 'EXPLICIT' // component needs to be initialized explicitly by calling Provider.initialize()
2324
}
2425

2526
/**

0 commit comments

Comments
 (0)