diff --git a/src/memoizerific.js b/src/memoizerific.js index fcf34e9..b6115b2 100644 --- a/src/memoizerific.js +++ b/src/memoizerific.js @@ -1,9 +1,13 @@ var MapOrSimilar = require('map-or-similar'); -module.exports = function (limit) { +module.exports = function (limit, normalizerFn) { var cache = new MapOrSimilar(process.env.FORCE_SIMILAR_INSTEAD_OF_MAP === 'true'), lru = []; + if (normalizerFn === undefined) { + normalizerFn = function (obj) { return obj; }; + } + return function (fn) { var memoizerific = function () { var currentCache = cache, @@ -20,15 +24,16 @@ module.exports = function (limit) { // loop through each argument to traverse the map tree for (i = 0; i < argsLengthMinusOne; i++) { + var key = normalizerFn(arguments[i]); lruPath[i] = { cacheItem: currentCache, - arg: arguments[i] + arg: key }; // climb through the hierarchical map tree until the second-last argument has been found, or an argument is missing. // if all arguments up to the second-last have been found, this will potentially be a cache hit (determined later) - if (currentCache.has(arguments[i])) { - currentCache = currentCache.get(arguments[i]); + if (currentCache.has(key)) { + currentCache = currentCache.get(key); continue; } @@ -36,14 +41,15 @@ module.exports = function (limit) { // make maps until last value newMap = new MapOrSimilar(process.env.FORCE_SIMILAR_INSTEAD_OF_MAP === 'true'); - currentCache.set(arguments[i], newMap); + currentCache.set(key, newMap); currentCache = newMap; } + var key = normalizerFn(arguments[argsLengthMinusOne]); // we are at the last arg, check if it is really memoized if (isMemoized) { - if (currentCache.has(arguments[argsLengthMinusOne])) { - fnResult = currentCache.get(arguments[argsLengthMinusOne]); + if (currentCache.has(key)) { + fnResult = currentCache.get(key); } else { isMemoized = false; @@ -53,14 +59,14 @@ module.exports = function (limit) { // if the result wasn't memoized, compute it and cache it if (!isMemoized) { fnResult = fn.apply(null, arguments); - currentCache.set(arguments[argsLengthMinusOne], fnResult); + currentCache.set(key, fnResult); } // if there is a cache limit, purge any extra results if (limit > 0) { lruPath[argsLengthMinusOne] = { cacheItem: currentCache, - arg: arguments[argsLengthMinusOne] + arg: key }; if (isMemoized) { @@ -138,4 +144,4 @@ function removeCachedResult(removedLru) { // check if the numbers are equal, or whether they are both precisely NaN (isNaN returns true for all non-numbers) function isEqual(val1, val2) { return val1 === val2 || (val1 !== val1 && val2 !== val2); -} \ No newline at end of file +} diff --git a/tests/custom-normalizer.js b/tests/custom-normalizer.js new file mode 100644 index 0000000..76d3cd5 --- /dev/null +++ b/tests/custom-normalizer.js @@ -0,0 +1,35 @@ +var Memoizerific = require('../src/memoizerific'); + +describe("null args", () => { + var memoizedFn, + arg1 = null, + arg2 = undefined, + arg3 = NaN; // important to test since NaN does not equal NaN + + beforeEach(function() { + memoizedFn = Memoizerific(50)(function(arg1, arg2, arg3) { + return ''; + }); + memoizedFn(arg1, arg2, arg3); + }); + + it("should be map or similar", () => { expect(memoizedFn.cache instanceof Map).toEqual(process.env.FORCE_SIMILAR_INSTEAD_OF_MAP !== 'true'); }); + + it("should not be memoized", () => { + expect(memoizedFn.wasMemoized).toEqual(false); + expect(memoizedFn.lru.length).toEqual(1); + }); + + it("should be memoized", () => { + memoizedFn(arg1, arg2, arg3); + expect(memoizedFn.wasMemoized).toEqual(true); + expect(memoizedFn.lru.length).toEqual(1); + }); + + it("should have multiple cached items", () => { + memoizedFn(arg1, arg2, arg3); + memoizedFn(arg1, arg2, 1); + expect(memoizedFn.wasMemoized).toEqual(false); + expect(memoizedFn.lru.length).toEqual(2); + }); +}); diff --git a/tests/null-args.js b/tests/null-args.js index 76d3cd5..bb4cb5e 100644 --- a/tests/null-args.js +++ b/tests/null-args.js @@ -1,35 +1,34 @@ var Memoizerific = require('../src/memoizerific'); -describe("null args", () => { - var memoizedFn, - arg1 = null, - arg2 = undefined, - arg3 = NaN; // important to test since NaN does not equal NaN - - beforeEach(function() { - memoizedFn = Memoizerific(50)(function(arg1, arg2, arg3) { - return ''; - }); - memoizedFn(arg1, arg2, arg3); - }); +describe("custom normalizer", () => { + var memoizedFn; - it("should be map or similar", () => { expect(memoizedFn.cache instanceof Map).toEqual(process.env.FORCE_SIMILAR_INSTEAD_OF_MAP !== 'true'); }); + it("should always be memoized", () => { + var memoizedFn = + Memoizerific(100, function () { return true; }) + (function (obj) { return obj; }); - it("should not be memoized", () => { - expect(memoizedFn.wasMemoized).toEqual(false); - expect(memoizedFn.lru.length).toEqual(1); - }); + memoizedFn({a: 1}); - it("should be memoized", () => { - memoizedFn(arg1, arg2, arg3); + memoizedFn(1); + expect(memoizedFn.wasMemoized).toEqual(true); + memoizedFn([1, 2, 3]); expect(memoizedFn.wasMemoized).toEqual(true); - expect(memoizedFn.lru.length).toEqual(1); }); - it("should have multiple cached items", () => { - memoizedFn(arg1, arg2, arg3); - memoizedFn(arg1, arg2, 1); + it("should be memoized based on custom function", () => { + var memoizedFn = + Memoizerific(100, function (i) { return i % 2; }) + (function (obj) { return obj; }); + + memoizedFn(1); + memoizedFn(3); + expect(memoizedFn.wasMemoized).toEqual(true); + + memoizedFn(2); expect(memoizedFn.wasMemoized).toEqual(false); - expect(memoizedFn.lru.length).toEqual(2); + + memoizedFn(4); + expect(memoizedFn.wasMemoized).toEqual(true); }); });