Skip to content

Commit

Permalink
feat(util): add optional synchronous callback to whenSet
Browse files Browse the repository at this point in the history
  • Loading branch information
asyncLiz committed Aug 24, 2018
1 parent cc6dadf commit 85c99d4
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 2 deletions.
34 changes: 34 additions & 0 deletions util/src/when-set.spec.ts
Expand Up @@ -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');
});
});
});
34 changes: 32 additions & 2 deletions util/src/when-set.ts
Expand Up @@ -4,6 +4,7 @@
* target and property to resolve.
*/
let whenSetMap: WeakMap<any, Map<PropertyKey, Promise<any>>>;
let callbackSyncMap: WeakMap<Promise<any>, Array<(value: any) => void>>;

/**
* Resolves when the provided property is set to a non-undefined value on the
Expand All @@ -14,6 +15,9 @@ let whenSetMap: WeakMap<any, Map<PropertyKey, Promise<any>>>;
* @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<
Expand All @@ -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<V> {
let currentValue = target[property];
if (predicate(currentValue)) {
if (typeof callbackSync === 'function') {
callbackSync(<V>target[property]);
}

return Promise.resolve(<V>target[property]);
} else {
if (!whenSetMap) {
whenSetMap = new WeakMap();
}

if (!callbackSyncMap) {
callbackSyncMap = new WeakMap();
}

let propertyPromiseMap: Map<K, Promise<V>>;
if (!whenSetMap.has(target)) {
propertyPromiseMap = new Map();
Expand All @@ -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<V>(resolve => {
Object.defineProperty(target, property, {
Expand All @@ -66,13 +85,24 @@ export function whenSet<
whenSetMap.delete(target);
}

const callbacks = callbackSyncMap.get(promise)!;
callbacks.forEach(callback => {
callback(value);
});

resolve(value);
}
}
});
});

propertyPromiseMap.set(property, promise);
const callbacks: Array<(value: V) => void> = [];
if (typeof callbackSync === 'function') {
callbacks.push(callbackSync);
}

callbackSyncMap.set(promise, callbacks);
return promise;
}
}
Expand Down

0 comments on commit 85c99d4

Please sign in to comment.