loading...
Cover image for Build a function memoizer [Part-2]

Build a function memoizer [Part-2]

ksankar profile image Kailash Sankar ・3 min read

memoizer (4 Part Series)

1) Build a function memoizer [Part-1] 2) Build a function memoizer [Part-2] 3) Build a function memoizer [Part-3] 4) Build a function memoizer [Part-4]

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

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

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
})();

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;
}

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

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

memoizer (4 Part Series)

1) Build a function memoizer [Part-1] 2) Build a function memoizer [Part-2] 3) Build a function memoizer [Part-3] 4) Build a function memoizer [Part-4]

Posted on by:

ksankar profile

Kailash Sankar

@ksankar

I'm a full stack web developer, jack of many and master of none.

Discussion

markdown guide