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.
Top comments (1)
Good Article!! just one thing i noticed is that your memoized generic function won't work if the function have more than 1 arguments.