DEV Community

loading...
Cover image for How To Cache Your Javascript Functions ? Meet the Memoization Technique!

How To Cache Your Javascript Functions ? Meet the Memoization Technique!

Ahmed Zeno
I am a software engineer who is interested in sharing some knowledge ('' ,) i decide that i will will post everyday problem that i am facing in my work. i hope that this will help other people too
・4 min read

First : what was the problem today ?

I had a simple React-Admin form with a Text Input Component (That writes the UserName) and some other components, and this Text input had a validate function which was calling an api and checking if the entered UserName wasn’t already taken.

The problem was that whenever I changed other component values the validate function was triggered , because this is how react admin form works.

And i wanted to have a function that cache the entered username locally and only do the api call if the username wasn’t checked already.

In normal situations this is no problem , you can just manipulate the time to call the validation for example when your component loses focus like using onBlure listener.

However as i said before the react admin behaves in a different way, so i was reading around and i end up reading about Function Memoization

And i thought i should share with you guys what i found and how it worked for me.

So what is Memoization Technique?

Memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

Well i found this great article from this great Author Divyanshu Maithani
On freecodecap.org you can check it here https://bit.ly/3dIfunv

who I am quitting and using some examples from his article to help spread the knowledge.

What does this mean?

Memoizing in simple terms means memorizing or storing in memory. A memoized function is usually faster because if the function is called subsequently with the previous value(s), then instead of executing the function, we would be fetching the result from the cache.

Ok, show me some examples!

Lets say that you have a function that return the factorial of a number
Lets call it getFactorial

function getFactorial(n) {
    // Calculations: n * (n-1) * (n-2) * ... (2) * (1)
    return factorial
}

Great, now let’s find

getFactorial(50)

The computer will perform calculations and return us the final answer, sweet!
When that’s done, let’s find

getFactorial(51)

The computer again performs a number of calculations and gets us the result, but you might have noticed that we’re already repeating a number of steps that could have been avoided.

An optimized way would be:

getFactorial(51) = getFactorial(50) * 51

But our function performs the calculations from scratch every time it’s called:

getFactorial(51) = 51 * 50 * 49 * ... * 2 * 1

Wouldn’t it be cool if somehow our getFactorial function could remember the values from its previous calculations and use them to speed up the execution?
Here’s what a simple memoized function might look like

// a simple function to add something

const add = (n) => (n + 10);
add(9);

// a simple memoized function to add something

const memoizedAdd = () => {
  let cache = {};
  return (n) => {
    if (n in cache) {
      console.log('Fetching from cache');
      return cache[n];
    }
    else {
      console.log('Calculating result');
      let result = n + 10;
      cache[n] = result;
      return result;
    }
  }
}

// returned function from memoizedAdd

const newAdd = memoizedAdd();

console.log(newAdd(9)); // calculated

console.log(newAdd(9)); // cached

Some takeaways from the above code are:

--- memoizedAdd returns a function which is invoked later. This is possible because in JavaScript, functions are first class objects which lets us use them as higher order functions and return another function.

--- cache can remember its values since the returned function has a closure over it.

--- It’s essential that the memoized function is pure. A pure function will return the same output for a particular input no matter how many times it’s called, which makes the cache work as expected.

So how to write a generic memoized function ?

const memoizedFunction = (fn) => {
  let cache = {};
  return (value) => {    
    if (value in cache) {
      console.log('Fetching from cache');
      return cache[value];
    }
    else {
      console.log('executing and fetching results');
      let result = fn(value);
      cache[value] = result;
      return result;
    }
  }
}

Now you can pass your function to this memoizedFunction .
In my case, my function was like this:-

const alreadyExists = async value => {
   return dataProvider
       .getOne(username, {id: value})
       .then(({data}) => (data && data.id ? 'UserName Already Exists' : null ))
       .catch(error => {
           console.log(error)
       })
}
const memoizedUsername = memoizedFunction(alreadyExists);
const validateUsername = [required(), minLength(3),memoizedUsername]
<SimpleForm
   toolbar={<CreateToolbar />}
   redirect="list"
   validate={validateTimeframe}
   validateOnBlur
   submitOnEnter={false}
  >
  <TextInput
       source="voucherCode"
       validate={validateUsername }
   />
.
.
.
</SimpleForm>

So for example

 console.log(validateUsername(SuperHero));
  // calculated and will return null
 console.log(validateUsername(SuperHero));
  // cached and will return UserName Already Exists
 console.log(validateUsername(username1234));
  // calculated  and will return null

Is memoization same as caching?

Yes, kind of. Memoization is actually a specific type of caching. While caching can refer in general to any storing technique (like HTTP caching ) for future use, memoizing specifically involves caching the return values of a function.

What is the limitation of Memoization?

--- In order to memoize a function, it should be pure so that return values are the same for same inputs every time.

--- Memoizing is a trade-off between added space and added speed and thus only significant for functions having a limited input range so that cached values can be made use of more frequently.

--- It might look like you should memoize your API calls however it isn’t necessary because the browser automatically caches them for you. See HTTP caching for more detail.

--- The best use case I found for memoized functions is for heavy computational functions which can significantly improve performance (factorial and fibonacci are not really good real world examples).

--- If you’re into React/Redux you can check out reselect which uses a memoized selector to ensure that calculations only happen when a change happens in a related part of the state tree.

Discussion (1)

Collapse
rahuljain983 profile image
rahuljain983

Good Article!! just one thing i noticed is that your memoized generic function won't work if the function have more than 1 arguments.