DEV Community

Cover image for Build a function memoizer [Part-2]
Kailash Sankar
Kailash Sankar

Posted on

Build a function memoizer [Part-2]

Continuing where we left off in the last part, we'll start by adding support for complex input parameters like Objects and Arrays.

The easiest way to create a unique key for complex params would be to JSON.stringify the input params. MDN has note that stringify does not guarantee any specific order but it is good enough for now. There are npm modules which can ensure a consistent hash.

Update the cache key generating function


// build cache key
const generateCacheKey = (args) => args.map((x) => JSON.stringify(x)).join("-");

// test
console.log(generateCacheKey([3, { x: "hello", y: "world" }, [81, "on3"], 22]));
// output: 3-{"x":"hello","y":"world"}-[81,"on3"]-22

Enter fullscreen mode Exit fullscreen mode

Test if caching is working for array/object params


// new test function with inputs - array, number and object
let count = 0;
function calc(values, multiplier, labels) {
  count++;
  const total = values.reduce((acc, x) => x + acc, 0) * multiplier;
  return `${labels.text} => ${total}`;
}


prettyPrint(memoizedCalc([10, 2], 2, { text: "A" }));
// result: A => 24, count: 1
prettyPrint(memoizedCalc([1], 1, { text: "B" }));
// result: B => 1, count: 2
prettyPrint(memoizedCalc([10, 2], 2, { text: "A" }));
// result: A => 24, count: 2
Enter fullscreen mode Exit fullscreen mode

The count remained the same, so our caching now supports complex inputs.

Let's see what happens when we use the memoizer for an asynchronous function.

// function to call mock api
  let count = 0;
  async function getTodo(id) {
    count++;
    return fetch(
      `https://jsonplaceholder.typicode.com/todos/${id}`
    ).then((res) => res.json());
  }

const memoizedGetTodo = memoizer(getTodo);

// call async functions
(async function () {
  prettyPrint(await memoizedGetTodo(1));
  // output: result: {...}, count: 1
  prettyPrint(await memoizedGetTodo(2));
  // output: result: {...}, count: 2
  prettyPrint(await memoizedGetTodo(1));
  // output: result: {...}, count: 2
})();
Enter fullscreen mode Exit fullscreen mode

It's working for async! The memoizer we wrote in Part-1 already supports async methods which return a promise.

How? On the first call, the code will cache an unresolved promise and immediately return a reference to it.
If cache is dumped, you will see something like
'1': Promise { <pending> }
The caller awaits for resolve, when it triggers the promise in cache becomes resolved and execution continues.
'1': Promise { { userId: 1, id: 1 ....} }
Now, we have in cache, a resolved promise which will then be returned whenever we see the same input parameters.

The next item in our list is a clear function which will allow the caller to clear the cache in closure. We have to re-write a bit of the memoizer as below to include the clear action.

function memoizer(fn) {
  // cache store
  let resultsCache = {};

  // memoized wrapper function
  // capture all the input args
  function memoized(...args) {
    const cacheKey = generateCacheKey(args);

    if (!(cacheKey in resultsCache)) {
      // cached value not found, call fn and cache result
      resultsCache[cacheKey] = fn(...args);
    }

    //console.log("cache", resultsCache);

    // return result from cache;
    return resultsCache[cacheKey];
  }

  // clear cache
  memoized.clearCache = () => {
    resultsCache = {};
  };

  return memoized;
}
Enter fullscreen mode Exit fullscreen mode

Let's see if it works as expected

  prettyPrint(await memoizedGetTodo(1));
  // output: result: {...}, count: 1
  prettyPrint(await memoizedGetTodo(2));
  // output: result: {...}, count: 2
  prettyPrint(await memoizedGetTodo(1));
  // result: {...}, count: 2
  memoizedGetTodo.clearCache(); // clear the results cache
  prettyPrint(await memoizedGetTodo(1));
  // result: {...}, count: 3
Enter fullscreen mode Exit fullscreen mode

Clearing the cache resulted in the last call hitting the base function and incrementing the counter to 3.

The next part of series will add support for setting cache size limit.


Photo by Jr Korpa on Unsplash

Oldest comments (0)