A higher-order solution
If we don't want to modify the original function, we'll create a higher-order function, which we'll, inspiredly, name once(). This function will receive a function as a parameter and will return a new function, which will work only a single time. (We'll be seeing more of higher-order functions in Chapter 6, Producing Functions - Higher-Order Functions; in particular, see section Doing things once, revisited.)
Underscore and LoDash already has a similar function, invoked as _.once(). Ramda also provides R.once(), and most FP libraries include similar functionality, so you wouldn't have to program it on your own.
Our once() function way seems imposing at first, but as you get accustomed to working in FP fashion, you'll get used to this sort of code, and will find it to be quite understandable:
const once = fn => {
let done = false;
return (...args) => {
if (!done) {
done = true;
fn(...args);
}
};
};
Let's go over some of the finer points of this function:
- The first line shows that once() receives a function (fn()) as its parameter.
- We are defining an internal, private done variable, by taking advantage of a closure, as in Solution #7, previously. We opted not to call it clicked, as previously, because you don't necessarily need to click on a button to call the function; we went for a more general term.
- The line return (...args) => ... says that once() will return a function, with some (0, 1, or more) parameters. Note that we are using the spread syntax we saw in Chapter 1, Becoming Functional - Several Questions. With older versions of JS you'd have to work with the arguments object; see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/arguments for more on that. The ES8 way is simpler and shorter!
- We assign done = true before calling fn(), just in case that function throws an exception. Of course, if you don't want to disable the function unless it has successfully ended, then you could move the assignment just below the fn() call.
- After the setting is done, we finally call the original function. Note the use of the spread operator to pass along whatever parameters the original fn() had.
So, how would we use it? We don't even need to store the newly generated function in any place; we can simply write the onclick method, as shown as follows:
<button id="billButton" onclick="once(billTheUser)(some, sales, data)">
Bill me
</button>;
Pay close attention to the syntax! When the user clicks on the button, the function that gets called with the (some, sales, data) argument isn't billTheUser(), but rather the result of having called once() with billTheUser as a parameter. That result is the one that can be called only a single time.
Note that our once() function uses functions as first-class objects, arrow functions, closures, and the spread operator; back in Chapter 1, Becoming Functional - Several Questions, we said we'd be needing those, so we're keeping our word! All we are missing here from that chapter is recursion... but as the Rolling Stones sing, You Can't Always Get What You Want!