<button id=“billButton” onclick=“billTheUser(some, sales, data)”>Bill me</button>
And, among the scripts you’d have something similar to this:
function billTheUser(some, sales, data) {
window.alert("Billing the user...");
// actually bill the user
}
(Assigning events handler directly in HTML, the way I did it, isn’t recommended. Rather, in unobtrusive fashion, you should assign the handlerthrough code. So... Do as I say, not as I do!)
This is a very barebone explanation of the problem and your web page, but it's enough for our purposes. Let's now get to think about ways of avoiding repeated clicks on that button… How can you manage to avoid the user clicking more than once?
We’ll consider several common solutions for this, and then analyze a functional way of solving the problem.OK, how many ways can you think of, to solve our problem? Let us take several solutions one by one, and analyze their quality.
How can we solve the problem? The first so called solution may seem like a joke: do nothing, tell the user not to click twice, and hope for the best!
This is a weasel way of avoiding the problem, but I've seen several websites that just warn the user about the risks of clicking more than once, and actually do nothing to prevent the situation... the user got billed twice? we warned him... it’s his fault!
<button id="billButton" onclick="billTheUser(some, sales, data)">Bill me</button>
<b>WARNING: PRESS ONLY ONCE, DO NOT PRESS AGAIN!!</b>
OK, so this isn’t actually a solution; let's move on to more serious proposals...
The solution most people would probably think of first, is using some global variable to record whether the user has already clicked on the button. You'd define a flag named something like clicked, initialized with false. When the user clicks on the button, you check the flag. If clicked was false, you change it to true, and execute the function; otherwise, you don't do anything at all.
let clicked = false;
.
.
.
function billTheUser(some, sales, data) {
if (!clicked) {
clicked = true;
window.alert("Billing the user...");
// actually bill the user
}
}
This obviously works, but has several problems that must be addressed.
So, this isn’t a very good solution either... let's keep thinking!
We may go for a lateral kind of solution, and instead of having the function avoid repeated clicks, we might just remove the possibility of clicking altogether.
function billTheUser(some, sales, data) {
document.getElementById("billButton").onclick = null;
window.alert("Billing the user...");
// actually bill the user
}
This solution also has some problems.
We can enhance this solution a bit, and avoid coupling the function to the button, by providing the latter's id as an extra argument in the call. (This idea can also be applied to some of the solutions below.) The HTML part would be:
<button
id="billButton"
onclick="billTheUser('billButton', some, sales, data)"
>
Bill me
</button>;
(note the extra argument) and the called function would be:
function billTheUser(buttonId, some, sales, data) {
document.getElementById(buttonId).onclick = null;
window.alert("Billing the user...");
// actually bill the user
}
This solution is somewhat better. But, in essence, we are still using a global element: not a variable, but the onclick value. So, despite the enhancement, this isn't a very good solution too. Let's move on.
A variant to the previous solution would be not removing the click function, and rather assign a new one instead. We are using functions as first-class objects here, when we assign the alreadyBilled() function to the click event:
function alreadyBilled() {
window.alert("Your billing process is running; don't click, please.");
}
function billTheUser(some, sales, data) {
document.getElementById("billButton").onclick = alreadyBilled;
window.alert("Billing the user...");
// actually bill the user
}
Now here’s a good point in this solution: if the user clicks a second time, he'll get a pop-up warning not to do that, but he won't be billed again. (From the point of view of the user experience, it's better.) However, this solution still has the very same objections as the previous one (code coupled to the button, need to reset the handler, harder testing) so we won't consider this to be quite good anyway.
A similar idea: instead of removing the event handler, disable the button, so the user won't be able to click:
function billTheUser(some, sales, data) {
document.getElementById("billButton").setAttribute("disabled", "true");
window.alert("Billing the user...");
// actually bill the user
}
This also works, but we still have objections as for the previous solutions (coupling the code to button, need to re-enable the button, harder testing) so we don't like this solution either.
Another idea: instead of changing anything in the button, let's have the event handler change itself. The trick is in the second line; by assigning a new value to the billTheUser variable, we are actually dynamically changing what the function does! The first time you call the function, it will do its thing... but it will also change itself out of existence, by giving its name to a new function:
function billTheUser(some, sales, data) {
billTheUser = function() {};
window.alert("Billing the user...");
// actually bill the user
}
There’s a special trick in the solution. Functions are global, so the line billTheUser=...actually changes the function inner workings; from that point on, billTheUser will be a new (null) function. This solution is still hard to test. Even worse, how would you restore the functionality of billTheUser, setting it back to its original objective?
We can go back to the idea of using a flag, but instead of making it global (which was our main objection) we can use an Immediately Invoked Function Expression (IIFE) that, via a closure, makes clicked local to the function, and not visible anywhere else:
var billTheUser = (clicked => {
return (some, sales, data) => {
if (!clicked) {
clicked = true;
window.alert("Billing the user...");
// actually bill the user
}
};
})(false);
In particular, see how clicked gets its initial false value, from the call, at the end. This solution is along the lines of the global variable solution, but using a private, local variable is an enhancement. The only objection we could find is that you’ll have to rework every function that needs to be called only once, to work in this fashion. (And, as we’ll see below, our FP solution is similar in some ways to it.) OK, it’s not too hard to do, but don’t forget the Don’t Repeat Yourself (D.R.Y) advice!
Now that we've gone through several “normal” solutions, let’s try to be more general: after all, requiring that some function or other be executed only once, isn’t that outlandish, and may be required elsewhere! Let’s lay down some principles:
(The first principle listed above is the single responsibility principle (the S in S.O.L.I.D.) which states that every function should be responsible over a single functionality. For more on S.O.L.I.D., check the article by “Uncle Bob” (Robert C. Martin, who wrote the five principles) at http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod).
Can we do it? Yes; and we'll write a higher order function, which we'll be able to apply to any function, to produce a new function that will work only once. Let's see how!
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.
(By the way, libraries such as Underscore and LoDash already have a similar function, invoked as _.once(). Ramda also provides R.once(), and most FP libraries include similar functionality, so you wouldn’t actually have to program it on your own.)
Our once() function way seem 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:
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 (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.
We can run a simple test:
const squeak = a => console.log(a + " squeak!!");
squeak("original"); // "original squeak!!"
squeak("original"); // "original squeak!!"
squeak("original"); // "original squeak!!"
const squeakOnce = once(squeak);
squeakOnce("only once"); // "only once squeak!!"
squeakOnce("only once"); // no output
squeakOnce("only once"); // no output
In one of the solutions above, we mentioned that it would be a good idea to do something every time after the first, and not silently ignoring the user's clicks. We'll write a new higher order function, that takes a second parameter; a function to be called every time from the second call onward:
const onceAndAfter = (f, g) => {
let done = false;
return (...args) => {
if (!done) {
done = true;
f(...args);
} else {
g(...args);
}
};
};
We have ventured further in higher-order functions; onceAndAfter() takes two functions as parameters and produces yet a third one, that includes the other two within.
We could make onceAndAfter() more flexible, by giving a default value for g, along the lines of const onceAndAfter = (f, g = ()=>{})... so if you didn't want to specify the second function, it would still work fine, because it would then call a do-nothing function.
We can do a quick-and-dirty test, along with the same lines as we did earlier:
const squeak = () => console.log("squeak!!");
const creak = () => console.log("creak!!");
const makeSound = onceAndAfter(squeak, creak);
makeSound(); // "squeak!!"
makeSound(); // "creak!!"
makeSound(); // "creak!!"
makeSound(); // "creak!!"
So, we got an even more enhanced function! Just to practice with some more varieties and possibilities, can you solve these extra questions?
let sayA = () => console.log("A");
let sayB = () => console.log("B");
let alt = alternator(sayA, sayB);
alt(); // A
alt(); // B
alt(); // A
alt(); // B
alt(); // A
alt(); // B
To summarize, we've seen a simple problem, based on a real-life situation, and after analyzing several common ways of solving that, we chose a functional thinking solution. We saw how to apply FP to our problem, and we also found a more general higher order way that we could apply to similar problems, with no further code changes. Finally, we produced an even better solution, from the user experience point of view.
Functional Programming can be applied all throughout your code, to enhance and simplify your development. This has been just a simple example, using higher order functions, but FP goes beyond that and provides many more tools for better coding.
[author title="Author Bio"]The author, Federico Kereki, is currently a Subject Matter Expert at Globant, where he gets to use a good mixture of development frameworks, programming tools, and operating systems, such as JavaScript, Node.JS, React and Redux, SOA, Containers, and PHP, with both Windows and Linux. He has taught several computer science courses at Universidad de la República, Universidad ORT Uruguay, and Universidad de la Empresa, he has also written texts for these courses.
Federico has written several articles—on JavaScript, web development, and open source topics—for magazines such as Linux Journal and LinuxPro Magazine in the United States, Linux+ and Mundo Linux in Europe, and for web sites such as Linux and IBM Developer Works. Kereki has given talks on Functional Programming with JavaScript in public conferences (such as JSCONF 2016) and used these techniques to develop Internet systems for businesses in Uruguay and abroad.[/author]
Introduction to the Functional Programming
What is the difference between functional and object oriented programming?
Manipulating functions in functional programming