Last active
October 18, 2022 17:58
-
-
Save alexitaylor/c297a548db516166f7d77fb07914c79d to your computer and use it in GitHub Desktop.
TypeScript Memoize Util Function. Caches the result of async functions. Function's arguments are converted into hash key. Plus tests.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* createCacheKeyFromArgs | |
* | |
* @desc Creates a cache key from fn's arguments. | |
* | |
* @param {any[]} args Arguments from fn being memoized. | |
* @return {string} Returns a cache key. | |
*/ | |
export const createCacheKeyFromArgs = (args: any[]) => | |
args.reduce((cacheKey, arg) => (cacheKey += `_${typeof arg === 'object' ? JSON.stringify(args) : `${arg}`}_`), ''); | |
/** | |
* memoize | |
* | |
* @desc Creates a function that memoizes the result of fn. | |
* The arguments of the fn are used to create a cache key. | |
* | |
* @param {Function} fn The function to have its output memoized. | |
* @return {Function} Returns the new memoized function. | |
*/ | |
const memoize: (...args: any[]) => any = function(fn) { | |
const cache = new Map(); | |
return function() { | |
const cacheKey = createCacheKeyFromArgs(Array.from(arguments)); | |
if (cache.has(cacheKey)) { | |
return cache.get(cacheKey); | |
} | |
const asyncFn = fn.apply(this, arguments); | |
cache.set(cacheKey, asyncFn); | |
return asyncFn; | |
}; | |
}; | |
export default memoize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* =========================== | |
* =========================== | |
* Jest Tests | |
* =========================== | |
* =========================== | |
*/ | |
const expect = require('expect'); | |
const mocks = { | |
add: jest.fn((x, y) => x + y) | |
}; | |
const addMemoize = memoize(mocks.add); | |
afterEach(() => { | |
mocks.add.mockClear(); | |
}); | |
const asyncAddMemoize = memoize(function(x, y) { | |
let promise = new Promise(resolve => { | |
setTimeout(() => { | |
resolve(mocks.add(x, y)); | |
}, 500); | |
}); | |
return promise; | |
}); | |
describe('memoize', () => { | |
it('should create a cache key from arguments', () => { | |
// Arrange | |
const args = [1, 3]; | |
// Act | |
const cacheKey = createCacheKeyFromArgs(args); | |
// Assert | |
expect(cacheKey).toEqual('_1__3_'); | |
}); | |
it('should create a cache key from an object argument', () => { | |
// Arrange | |
const args = [{ a: 1 }]; | |
// Act | |
const cacheKey = createCacheKeyFromArgs(args); | |
// Assert | |
expect(cacheKey).toEqual('_[{"a":1}]_'); | |
}); | |
it('should return expected result of add fn', () => { | |
// Arrange / Act | |
const result = addMemoize(1, 2); | |
// Assert | |
expect(result).toEqual(3); | |
}); | |
it('should memoize the result of add fn and only call it once', () => { | |
expect.assertions(4); | |
// Arrange | |
const addSpy = jest.spyOn(mocks, 'add'); | |
// Act | |
const resultOne = addMemoize(2, 2); | |
const resultTwo = addMemoize(2, 2); | |
const resultThree = addMemoize(2, 2); | |
// Assert | |
expect(resultOne).toEqual(4); | |
expect(resultTwo).toEqual(4); | |
expect(resultThree).toEqual(4); | |
expect(addSpy).toBeCalledTimes(1); | |
}); | |
it('should return expected result of async add fn', async () => { | |
// Arrange / Act | |
const result = await asyncAddMemoize(2, 3); | |
// Assert | |
expect(result).toEqual(5); | |
}); | |
it('should memoize the result of add async fn and only call it once', async () => { | |
expect.assertions(4); | |
// Arrange | |
const addSpy = jest.spyOn(mocks, 'add'); | |
// Act | |
const [resultOne, resultTwo, resultThree] = await Promise.all([ | |
asyncAddMemoize(3, 3), | |
asyncAddMemoize(3, 3), | |
asyncAddMemoize(3, 3) | |
]); | |
// Assert | |
expect(resultOne).toEqual(6); | |
expect(resultTwo).toEqual(6); | |
expect(resultThree).toEqual(6); | |
expect(addSpy).toBeCalledTimes(1); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
super useful, thanks, here's a version that preserves types :)