How to run a function once
Often times, there is a need to perform a certain functionality only once in the software. This is a simple and common situation that is faced often times in software development. An example could be the welcome message shown on a website when the user registers for the first time. Or it could be a buy button at checkout that should deduct money only once (even if clicked multiple times). Another example could be the analytics metric that should be reported once throughout the user journey.
There are many ways to achieve this particular behaviour in code, depending upon the situation. Let’s see some of them and see which one stands out in terms of better usability and testing.
Solution 1 — Use a global state:
This might be the most obvious and simpler way of making sure that a function runs only once. A global variable can be use to keep track of the function to indicate if it has been already executed or not. A flag can be initialised as false inside the function, it will be toggled to prevent subsequent executions. Following is the demonstration of such solution:
let isUserWelcomed = false
const welcomeUserOnce = (user) => {
if (!isUserWelcomed) {
isUserWelcomed = true
console.log("welcome to home", user)
}
}
This is a valid solution, our welcome function will only run once, but there is a room for improvement:
- The issue with this solution is we are using a global variable
isUserWelcomed
to keep track of the state. This variable can be access by other parts of code, and thus modified accidentally. - The behaviour of this function depends on a certain value of the global variable. This flag must be initialised as
false
otherwise the behaviour would be different. - Testing it would be difficult as it depends on an external value.
Solution 2 — Use a local state:
We saw previously that the first solution is bad because of the use of global state. We can improve it by making the state variable isUserWelcomed
a private variable. Furthermore, we can use closure to hide the state variable, so it is inaccessible from outside the function.
const welcomeUserOnce = ((isUserWelcomed) => {
return (user) => {
if (!isUserWelcomed) {
isUserWelcomed = true
console.log("welcome to home", user)
}
}
})(false)
Here we are using immediately invoke function to pass the initial state which will always be false, and we return a new function which can access the state through closure. As previous solution, this will also do the job, but there is still some room for the improvement.
I believe this can be improved by making “run once” logic extendable. At the moment if any other piece of code needs to run once we have to repeat this logic. But being a good programmer, we shouldn’t repeat our self or in other words, our code should be DRY(Don’t Repeat Yourself advise.)
Solution 3 — A functional approach:
So far, we have seen multiple solutions to solve a single problem. Now we will use a more generic and functional way of doing the same thing. But first, let’s define our ideal solution, so we can set our expectations accordingly:
- Our solution should be reusable and could be used to make any function run only once.
- Our function should not modify the original function in any way.
- We should be able to restore the original function, so it can be re-run more than once if needed.
Okay, now we have our requirements, we can come up with a solution that can tick all of them. It is clear that our function needs to be a higher-order function, as we need to return a new copy — we cannot modify the original function. Our solution will be:
const once = fn => {
let done = false
return (…args) => {
if (!done) {
done = true
fn(…args)
}
}
}
Let’s look closely and see why and how this works:
- The signature of function
once
tells that it takes a function as a parameter and return a new function. - There is a local variable
done
to track if the passed function executed or not. - Finally, a new function with zero or more parameters is returned. We used spread operator syntax to do that.
Now we know how this once
function works, let's explore why this is better
- The
once
function can be reused to make any function of any kind into a "run once" function. We do not have to change anything inside the functions. The following example will showcase the usage of our functiononce
:
const greet = (name) => {
console.log('Hello ' + name)
}
const greetOnce = once(greet)
greetOnce('Alice')
// Output: Hello Alice
greetOnce('Bob')
// Output:
- We don’t rely on a global or external state. Everything needed is contained within the
once
function. This will make testing simpler and easier. - Actually, all the above benefits and qualities can be summed up into a one word, and that is Purity. The function
once
is better because it is pure.
A pure function always return the same output if provided the same parameters. And a pure function has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or input/output streams).
Conclusion:
In this post, we have seen different ways of achieving the functionality of running a function once. We compared different approaches, we were able to devise most effective and robust solution. It not only makes the logic reusable, but also Pure. I hope this example gave you a good example of pure functions and their pros.
I hope you found this post helpful and interesting. Please share, clap, and comment. I am really looking forward to your feedback and thoughts.