diff --git a/util/src/when-set.spec.ts b/util/src/when-set.spec.ts index 01f1140..3155c07 100644 --- a/util/src/when-set.spec.ts +++ b/util/src/when-set.spec.ts @@ -72,5 +72,39 @@ describe('util', () => { result = await promise; expect(result).toBe('bar'); }); + + it('should allow synchronous callbacks', () => { + const target: { foo?: string } = {}; + const callbackSync = jasmine.createSpy('callbackSync'); + whenSet(target, 'foo', undefined, callbackSync); + target.foo = 'bar'; + expect(callbackSync).toHaveBeenCalledWith('bar'); + expect(target.foo).toBe('bar'); + }); + + it('should allow synchronous callbacks when value already set', () => { + const target = { foo: 'bar' }; + const callbackSync = jasmine.createSpy('callbackSync'); + whenSet(target, 'foo', undefined, callbackSync); + expect(callbackSync).toHaveBeenCalledWith('bar'); + }); + + it('should allow multiple synchronous callbacks', () => { + const target: { foo?: string } = {}; + const callbacks = [ + jasmine.createSpy('callbackSync1'), + jasmine.createSpy('callbackSync2'), + jasmine.createSpy('callbackSync3') + ]; + + callbacks.forEach(callback => + whenSet(target, 'foo', undefined, callback) + ); + target.foo = 'bar'; + callbacks.forEach(callback => + expect(callback).toHaveBeenCalledWith('bar') + ); + expect(target.foo).toBe('bar'); + }); }); }); diff --git a/util/src/when-set.ts b/util/src/when-set.ts index 0d05df2..b45a7a6 100644 --- a/util/src/when-set.ts +++ b/util/src/when-set.ts @@ -4,6 +4,7 @@ * target and property to resolve. */ let whenSetMap: WeakMap>>; +let callbackSyncMap: WeakMap, Array<(value: any) => void>>; /** * Resolves when the provided property is set to a non-undefined value on the @@ -14,6 +15,9 @@ let whenSetMap: WeakMap>>; * @param predicate the predicate to determine whether or not the Promise * should resolve for a new value. The default is to check if the value is * not undefined. + * @param callbackSync if more precise timing is needed, this callback may be + * provided to immediately process the set value since the resolved Promise + * will be async * @returns a Promise that resolves with the new value */ export function whenSet< @@ -23,16 +27,25 @@ export function whenSet< >( target: T, property: K, - predicate = (value: any) => typeof value !== 'undefined' + predicate = (value: any) => typeof value !== 'undefined', + callbackSync?: (value: V) => void ): Promise { let currentValue = target[property]; if (predicate(currentValue)) { + if (typeof callbackSync === 'function') { + callbackSync(target[property]); + } + return Promise.resolve(target[property]); } else { if (!whenSetMap) { whenSetMap = new WeakMap(); } + if (!callbackSyncMap) { + callbackSyncMap = new WeakMap(); + } + let propertyPromiseMap: Map>; if (!whenSetMap.has(target)) { propertyPromiseMap = new Map(); @@ -42,7 +55,13 @@ export function whenSet< } if (propertyPromiseMap.has(property)) { - return propertyPromiseMap.get(property)!; + const promise = propertyPromiseMap.get(property)!; + if (typeof callbackSync === 'function') { + const callbacks = callbackSyncMap.get(promise)!; + callbacks.push(callbackSync); + } + + return promise; } else { const promise = new Promise(resolve => { Object.defineProperty(target, property, { @@ -66,6 +85,11 @@ export function whenSet< whenSetMap.delete(target); } + const callbacks = callbackSyncMap.get(promise)!; + callbacks.forEach(callback => { + callback(value); + }); + resolve(value); } } @@ -73,6 +97,12 @@ export function whenSet< }); propertyPromiseMap.set(property, promise); + const callbacks: Array<(value: V) => void> = []; + if (typeof callbackSync === 'function') { + callbacks.push(callbackSync); + } + + callbackSyncMap.set(promise, callbacks); return promise; } }