Skip to content

Commit cc55ef5

Browse files
committed
feat(context): add more utils to resolve valueOrPromises
1 parent 5b27323 commit cc55ef5

File tree

5 files changed

+226
-79
lines changed

5 files changed

+226
-79
lines changed

packages/context/src/binding.ts

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
BoundValue,
1414
ValueOrPromise,
1515
MapObject,
16+
transformValueOrPromise,
1617
} from './value-promise';
1718
import {Provider} from './provider';
1819

@@ -170,30 +171,16 @@ export class Binding<T = BoundValue> {
170171
): ValueOrPromise<T> {
171172
// Initialize the cache as a weakmap keyed by context
172173
if (!this._cache) this._cache = new WeakMap<Context, T>();
173-
if (isPromiseLike(result)) {
174-
if (this.scope === BindingScope.SINGLETON) {
175-
// Cache the value at owning context level
176-
result = result.then(val => {
177-
this._cache.set(ctx.getOwnerContext(this.key)!, val);
178-
return val;
179-
});
180-
} else if (this.scope === BindingScope.CONTEXT) {
181-
// Cache the value at the current context
182-
result = result.then(val => {
183-
this._cache.set(ctx, val);
184-
return val;
185-
});
186-
}
187-
} else {
174+
return transformValueOrPromise(result, val => {
188175
if (this.scope === BindingScope.SINGLETON) {
189176
// Cache the value
190-
this._cache.set(ctx.getOwnerContext(this.key)!, result);
177+
this._cache.set(ctx.getOwnerContext(this.key)!, val);
191178
} else if (this.scope === BindingScope.CONTEXT) {
192179
// Cache the value at the current context
193-
this._cache.set(ctx, result);
180+
this._cache.set(ctx, val);
194181
}
195-
}
196-
return result;
182+
return val;
183+
});
197184
}
198185

199186
/**
@@ -210,7 +197,7 @@ export class Binding<T = BoundValue> {
210197
*
211198
* ```
212199
* const result = binding.getValue(ctx);
213-
* if (isPromise(result)) {
200+
* if (isPromiseLike(result)) {
214201
* result.then(doSomething)
215202
* } else {
216203
* doSomething(result);
@@ -408,11 +395,7 @@ export class Binding<T = BoundValue> {
408395
ctx!,
409396
session,
410397
);
411-
if (isPromiseLike(providerOrPromise)) {
412-
return providerOrPromise.then(p => p.value());
413-
} else {
414-
return providerOrPromise.value();
415-
}
398+
return transformValueOrPromise(providerOrPromise, p => p.value());
416399
};
417400
return this;
418401
}

packages/context/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export {
1313
MapObject,
1414
resolveList,
1515
resolveMap,
16+
resolveUntil,
17+
transformValueOrPromise,
1618
tryWithFinally,
1719
getDeepProperty,
1820
} from './value-promise';

packages/context/src/resolver.ts

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {
1010
Constructor,
1111
ValueOrPromise,
1212
MapObject,
13-
isPromiseLike,
1413
resolveList,
1514
resolveMap,
15+
transformValueOrPromise,
1616
} from './value-promise';
1717

1818
import {
@@ -56,51 +56,23 @@ export function instantiateClass<T>(
5656
}
5757
const argsOrPromise = resolveInjectedArguments(ctor, '', ctx, session);
5858
const propertiesOrPromise = resolveInjectedProperties(ctor, ctx, session);
59-
let inst: ValueOrPromise<T>;
60-
if (isPromiseLike(argsOrPromise)) {
61-
// Instantiate the class asynchronously
62-
inst = argsOrPromise.then(args => {
59+
const inst: ValueOrPromise<T> = transformValueOrPromise(
60+
argsOrPromise,
61+
args => {
6362
/* istanbul ignore if */
6463
if (debug.enabled) {
6564
debug('Injected arguments for %s():', ctor.name, args);
6665
}
6766
return new ctor(...args);
68-
});
69-
} else {
67+
},
68+
);
69+
return transformValueOrPromise(propertiesOrPromise, props => {
7070
/* istanbul ignore if */
7171
if (debug.enabled) {
72-
debug('Injected arguments for %s():', ctor.name, argsOrPromise);
72+
debug('Injected properties for %s:', ctor.name, props);
7373
}
74-
// Instantiate the class synchronously
75-
inst = new ctor(...argsOrPromise);
76-
}
77-
if (isPromiseLike(propertiesOrPromise)) {
78-
return propertiesOrPromise.then(props => {
79-
/* istanbul ignore if */
80-
if (debug.enabled) {
81-
debug('Injected properties for %s:', ctor.name, props);
82-
}
83-
if (isPromiseLike(inst)) {
84-
// Inject the properties asynchronously
85-
return inst.then(obj => Object.assign(obj, props));
86-
} else {
87-
// Inject the properties synchronously
88-
return Object.assign(inst, props);
89-
}
90-
});
91-
} else {
92-
if (isPromiseLike(inst)) {
93-
/* istanbul ignore if */
94-
if (debug.enabled) {
95-
debug('Injected properties for %s:', ctor.name, propertiesOrPromise);
96-
}
97-
// Inject the properties asynchronously
98-
return inst.then(obj => Object.assign(obj, propertiesOrPromise));
99-
} else {
100-
// Inject the properties synchronously
101-
return Object.assign(inst, propertiesOrPromise);
102-
}
103-
}
74+
return transformValueOrPromise(inst, obj => Object.assign(obj, props));
75+
});
10476
}
10577

10678
/**
@@ -257,23 +229,13 @@ export function invokeMethod(
257229
typeof targetWithMethods[method] === 'function',
258230
`Method ${method} not found`,
259231
);
260-
if (isPromiseLike(argsOrPromise)) {
261-
// Invoke the target method asynchronously
262-
return argsOrPromise.then(args => {
263-
/* istanbul ignore if */
264-
if (debug.enabled) {
265-
debug('Injected arguments for %s:', methodName, args);
266-
}
267-
return targetWithMethods[method](...args);
268-
});
269-
} else {
232+
return transformValueOrPromise(argsOrPromise, args => {
270233
/* istanbul ignore if */
271234
if (debug.enabled) {
272-
debug('Injected arguments for %s:', methodName, argsOrPromise);
235+
debug('Injected arguments for %s:', methodName, args);
273236
}
274-
// Invoke the target method synchronously
275-
return targetWithMethods[method](...argsOrPromise);
276-
}
237+
return targetWithMethods[method](...args);
238+
});
277239
}
278240

279241
/**

packages/context/src/value-promise.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Node module: @loopback/context
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
5-
65
/**
76
* This module contains types for values and/or promises as well as a set of
87
* utility methods to handle values and/or promises.
@@ -218,3 +217,55 @@ export function tryWithFinally<T>(
218217
}
219218
return result;
220219
}
220+
221+
/**
222+
* Resolve an iterator of source values into a result until the evaluator
223+
* returns `true`
224+
* @param source The iterator of source values
225+
* @param resolver The resolve function that maps the source value to a result
226+
* @param evaluator The evaluate function that decides when to stop
227+
*/
228+
export function resolveUntil<T, V>(
229+
source: Iterator<T>,
230+
resolver: (sourceVal: T) => ValueOrPromise<V | undefined>,
231+
evaluator: (sourceVal: T, targetVal: V | undefined) => boolean,
232+
): ValueOrPromise<V | undefined> {
233+
// Do iteration in loop for synchronous values to avoid stack overflow
234+
while (true) {
235+
const next = source.next();
236+
if (next.done) return undefined; // End of the iterator
237+
const sourceVal = next.value;
238+
const valueOrPromise = resolver(sourceVal);
239+
if (isPromiseLike(valueOrPromise)) {
240+
return valueOrPromise.then(v => {
241+
if (evaluator(sourceVal, v)) {
242+
return v;
243+
} else {
244+
return resolveUntil(source, resolver, evaluator);
245+
}
246+
});
247+
} else {
248+
if (evaluator(sourceVal, valueOrPromise)) {
249+
return valueOrPromise;
250+
}
251+
// Continue with the while loop
252+
}
253+
}
254+
}
255+
256+
/**
257+
* Transform a value or promise with a function that produces a new value or
258+
* promise
259+
* @param valueOrPromise The value or promise
260+
* @param transformer A function that maps the source value to a value or promise
261+
*/
262+
export function transformValueOrPromise<T, V>(
263+
valueOrPromise: ValueOrPromise<T>,
264+
transformer: (val: T) => ValueOrPromise<V>,
265+
): ValueOrPromise<V> {
266+
if (isPromiseLike(valueOrPromise)) {
267+
return valueOrPromise.then(transformer);
268+
} else {
269+
return transformer(valueOrPromise);
270+
}
271+
}

0 commit comments

Comments
 (0)