Build a function memoizer [Part-2]
Tags: javascript, memo, interview
Published: 2020-07-25 19:39:38

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) but it is good enough for now. There are npm modules which can ensure a consistent [hash](https://www.npmjs.com/package/json-stringify-deterministic).

Update the cache key generating function
```javascript

// 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
```javascript

// 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_.

```javascript
// 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.
```javascript
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
```javascript
  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](https://unsplash.com/@korpa?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText) on [Unsplash](https://unsplash.com/t/textures-patterns?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)