Things can be tricky when you want to use a function (with required parameters) as an argument within another function.
The title to this point makes my brain hurt, so let's begin with an example. Say you have a basic JavaScript function that is a simple alias for console.log
:
function log(text) {
console.log(text);
}
And you have another function called runFunc
that lets you execute some function, which gets passed to runFunc
as the only argument.
function runFunc(fn) {
fn();
}
The question is: How can use log()
as the argument passed to runFunc()
**, given that** log
has a required parameter (**text
**)?
JavaScript beginners often start with this pattern, calling log()
within runFunc()
.
runFunc(log("Hello World"));
That's a logical first step, but unfortunately, it doesn't work.
When you use parentheses with a function name, you are calling the function — you're telling the JavaScript runtime to execute the function. What you want to do is define a function to be used with runFunc
, so that runFunc
simply has a reference to the log
function, which it can run at the appropriate time within its code.
This problem becomes more elusive to solve because it's not always immediately obvious that there is a problem.
In our case, "Hello World" will still be logged — everything appears to work fine.
But if we also log inside the runFunc
method, we can see that log
is executed before runFunc
.
function log(text) {
console.log(text);
}
function runFunc(fn) {
console.log("Executing runFunc ...");
fn();
}
runFunc(log("Hello World"));
// => "Hello World"
// => "Executing runFunc ..."
Instead, we can define an anonymous function as an argument, and then run log
inside that function.
function log(text) {
console.log(text);
}
function runFunc(fn) {
console.log("Executing runFunc ...");
fn();
}
runFunc(function () {
log("Hello World");
});
// => "Executing runFunc ..."
// => "Hello World"
And now we see the results in the correct order!
The reason this works is not that the function is anonymous. It's because we didn't run the function, we just defined it.
You could definitely still cause a problem by running the anonymous function, like this:
runFunc(
(function () {
log("Hello World");
})()
); // Notice the extra ()
Likewise, you could also use a named function that they calls the log
function.
function logHello() {
log("Hello World");
}
runFunc(logHello);
I don't like this pattern because logHello
is so specific that we're unlikely to reuse it elsewhere in the application, which devalues abstracting it into its own function.
As a real-world example, see this practice in action when working with addEventListener
.
Although it may not always be an option, when you have control over runFunc
, another approach is to add additional parameters to represent arguments that you can pass onto the function within runFunc
.
function log(text) {
console.log(text);
}
function runFunc(fn, arg) {
fn(arg);
}
runFunc(log, "Hello World");
And you could even use the spread operator to account for multiple arguments being passed to the interior function.
function log(a, b) {
console.log(a, b);
}
function runFunc(fn, ...args) {
fn(...args);
}
runFunc(log, "Hello", "World");
// => "Hello"
// => "World"