Skip to content

Commit

Permalink
feat(memoize): adding new util function to memoize results
Browse files Browse the repository at this point in the history
  • Loading branch information
roggervalf committed Apr 30, 2020
1 parent 9d2408f commit c55032d
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 1 deletion.
86 changes: 86 additions & 0 deletions src/memoize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { memoize } from './memoize';

describe('memoize', () => {
it('should memoize results based on the first argument given', () => {
const values = memoize((a: number, b: number, c: number) => {
return a + b + c;
});

expect(values(1, 2, 3)).toEqual(6);
expect(values(1, 3, 5)).toEqual(6);
});

it('should support a `resolver`', () => {
const fn = function(a: number, b: number, c: number): number {
return a + b + c;
};
const memoized = memoize(fn, fn);

expect(memoized(1, 2, 3)).toEqual(6);
expect(memoized(1, 3, 5)).toEqual(9);
});

it('should use `this` binding of function for `resolver`', () => {
const fn = function(a: number): number {
return a + this.b + this.c;
};
const memoized = memoize(fn, fn);
const object = { memoized: memoized, b: 2, c: 3 };

expect(object.memoized(1)).toEqual(6);
object.b = 3;
object.c = 5;

expect(object.memoized(1)).toEqual(9);
});

it('should check cache for own properties', () => {
const props = [
'constructor',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'toLocaleString',
'toString',
'valueOf'
];

function identity<T>(value: T): T {
return value;
}

const memoized = memoize(identity);

const values = props.map(value => memoized(value));

expect(values).toEqual(props);
});

it('should cache the `__proto__` key', () => {
const array = [];
const key = '__proto__';

function identity<T>(value: T): T {
return value;
}

[0, 1].forEach((value, index): void => {
let count = 0;
const resolver = index ? identity : undefined;

const memoized = memoize(() => {
count++;
return array;
}, resolver);

const cache = memoized.cache;

memoized(key);
memoized(key);

expect(count).toEqual(1);
expect(cache.get(key)).toEqual(array);
expect(cache.delete(key)).toEqual(true);
});
});
});
62 changes: 62 additions & 0 deletions src/memoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MemoizeInterface } from './types';

/**
* Creates a function that memoizes the result of `func`. If `resolver` is
* provided, it determines the cache key for storing the result based on the
* arguments provided to the memoized function. By default, the first argument
* provided to the memoized function is used as the map cache key. The `func`
* is invoked with the `this` binding of the memoized function.
*
* @since 3.1.0
* @category Function
* @param {Function} func The function to have its output memoized.
* @param {Function} [resolver] The function to resolve the cache key.
* @returns {Function} Returns the new memoized function.
* @example
*
* const object = { 'a': 1, 'b': 2 }
* const other = { 'c': 3, 'd': 4 }
*
* const values = memoize(values)
* values(object)
* // => [1, 2]
*
* values(other)
* // => [3, 4]
*
* object.a = 2
* values(object)
* // => [1, 2]
*
* // Modify the result cache.
* values.cache.set(object, ['a', 'b'])
* values(object)
* // => ['a', 'b']
*/
export function memoize(func: Function, resolver?: Function): MemoizeInterface {
const memoized = function(...args: any): MemoizeInterface {
const key = resolver ? resolver.apply(this, args) : args[0];
const cache = memoized.cache;

if (cache.has(key)) {
return cache.get(key);
}
const result = func.apply(this, args);
memoized.cache = cache.set(key, result) || cache;
return result;
};
memoized.cache = new Map();
return memoized;
}

/*const memoize = (fn: Function): Function => {
const cache = {};
return (...args): any => {
const stringifiedArgs = JSON.stringify(args);
const result = (cache[stringifiedArgs] =
typeof cache[stringifiedArgs] === 'undefined'
? fn(...args)
: cache[stringifiedArgs]);
return result;
};
};*/
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ export interface EvaluateResourceBasedInterface
context?: Context;
}

export interface MemoizeInterface extends Function {
cache: Map<any, any>;
}

type IdentityBasedType = StatementInterface &
(ActionBlock | NotActionBlock) &
(ResourceBlock | NotResourceBlock);
Expand Down
3 changes: 2 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
* @returns {boolean} Returns true if `object` has `action` attribute.
* @example
* ```javascript
* instanceOfActionBlock({ action: 'something' }) => true
* instanceOfActionBlock({ action: 'something' })
* // => true
* ```
*/
export function instanceOfActionBlock(object: object): object is ActionBlock {
Expand Down

0 comments on commit c55032d

Please sign in to comment.