Let’s consider a simplified version of this code.
Before you added some important context to your question (the fact that myFunction()
is actually being used, and how it’s used), I wasn’t quite sure where your confusion was, so this answer starts with a general explanation of callbacks, higher-order functions, and currying.
First, let’s turn the function
into an arrow function, so the code is more straight-forward to read.
There are differences between them, but let’s ignore them for now.
Let’s also rename myFunction
to outer
, so we know which function we’re talking about.
const outer = () => {
return (caller) => {
caller(something);
};
};
outer
is a function.
outer()
is also a function; that means, the return value of the function outer
is, itself, a function.
Since outer
is a function that returns a function, this makes outer
a higher-order function (HOF) (according to one of the two definitions of HOFs)1.
To be explicit, you can reference the returned function by using another variable:
const inner = outer();
How come that I don’t get an error when the caller
argument is actually not defined?
That’s a strange question to ask; when outer()
is called, it doesn’t touch on caller
yet.
Only calling inner
does.
Ask yourself this: why do you not get an error for a
and b
not being defined here?2
const sum = (a, b) => a + b;
sum;
That’s right: sum
isn’t even called!
Just like outer()
isn’t even called.
Even one step further, this won’t throw an error even if somethingUndefined
isn’t defined anywhere and cannot be set anywhere.
(But you will get linter warnings.)
const someFunction = () => somethingUndefined;
someFunction;
Why do you not get an error here, in this common example of HOFs, despite b
not being defined at the point where add(5)
is assigned to addFive
?
const add = (a) => {
return (b) => a + b;
};
// Or more simply:
// const add = (a) => (b) => a + b;
const addFive = add(5);
console.log(addFive(3)); // Pints 8.
Because the function that needs b
, i.e. addFive
, has not been called yet.
If you want to force an error due to caller
not being defined, you’d have to call the function that actually needs caller
defined. The function in question is outer()
! You call it, like any other function, with ()
: outer()()
. Now you get a TypeError
because caller
isn’t a function.
This “double call” is called currying.
The code can also be rewritten like this.
The only difference is that inner
is now also accessible in the outer scope where outer
is.
See What is the scope of variables in JavaScript?.
const inner = (caller) => {
caller(something);
};
const outer = () => inner;
inner
takes the role of the returned function (outer()
).
It takes a function as an argument and calls it with something
as an argument.
outer
is just a function that returns the function inner
and nothing else.
It does not call it, it doesn’t touch on caller
, it has nothing to do with something
.
outer()
and inner
are identical.
On the other hand, if I omit it and write the code [without caller
], those two functions firstFuction()
and secondFunction()
aren’t executed. So, how exactly does that caller
— or however it’s called — work?
First, let’s fix the semantics: firstFuction()
and secondFunction()
are function calls.
Whether they are functions or not, depends on what firstFuction
and secondFunction
return.
firstFuction
and secondFunction
are functions (at least the code implies that they are).
More specifically, consider alert
:
alert
is a function; you can call it like alert()
.
alert()
is not a function; you cannot call it like alert()()
.
caller
is a callback function.
If you omit caller
from the code, then myFunction()()
would be the proper call that doesn’t throw an error and simply calls firstFuction
and secondFunction
.
But the point of the callback is to pass the results of firstFuction
and secondFunction
to the function caller
.
You have to provide that function yourself, e.g. the alert
function.
myFunction()(alert)
calls the inner function, passing alert
to it.
The inner function calls firstFuction
and secondFunction
and passes their results to the caller
, which we specified to be alert
.
Could you please tell me how it works once I call it like myFunction()
?
Quite simply, nothing happens here.
myFunction
returns a function, and that’s it; that function isn’t used further, so it is discarded.
There is a key aspect of higher-order functions that isn’t demonstrated in this code: encapsulating values.
Consider this code:
const add = (a) => {
let count = 0;
return (b) => {
++count;
console.log(`The function to add ${a} to something else has been called ${count} time(s).`);
return a + b
};
};
add
and b
work similarly to myFunction
and caller
from before, but now, the outer function also needs an argument, so add()
won’t help.
add(5)
calls the add
function and sets argument a
to 5
.
Further, add(5)(3)
calls the inner, returned function, sets b
to 3
and returns 8
.
But there’s also some state inside add
in the form of a variable called count
.
const addFive = add(5);
console.log(addFive(3)); // Prints 8.
// But also logs: "The function to add 5 to something else has been called 1 time(s)."
console.log(addFive(10)); // Prints 15.
// But also logs: "The function to add 5 to something else has been called 2 time(s)."
count
is only accessible inside outer
(including any function inside of it); it is “encapsulated”.
This is a common way to hide some state that should only be accessible inside a specific function.
a
is actually also encapsulated inside add(
…)
; a
and count
have the same scope and aren’t that different from each other, just that a
can be set as an argument when calling add
whereas count
cannot.
You’ve then added more context, where the call looks more like this:
someOtherFunction(myFunction())
This is different from the situation before where you just wrote the call like myFunction()
.
Now, the result is not discarded, as it is fed into someOtherFunction
(in the original code: store.dispatch
).
[caller
] is nowhere passed as an argument and it is not defined anywhere.
But now it is the job of someOtherFunction
to call myFunction()
with the appropriate callback function as an argument.
Now, you don’t pass the function yourself; instead, someOtherFunction
does it for you.
Compare this to Where do the parameters in a JavaScript callback function come from?.
If you go back to the sum
example function from earlier, then either you pass the arguments yourself:
sum(1, 2);
or some library or framework does it for you:
// Library code
const doTheRightThing = (callback) => callback(1, 2);
// Your code
doTheRightThing(sum);
or a built-in (or host-defined) function does it for you:
[ 1, 2, 3 ].reduce(sum, 0);
It doesn’t matter where these parameters come from or where the function is called from.
But it is called, and it is called with the right arguments somewhere.
1: The two definitions of a higher-order function are
- a function that returns a function, or
- a function that takes another function as an argument.
outer
fits the first definition.
outer()
(independently and coincidentally) fits the second definition.
2: Okay, granted, sum()
would also not throw an error, but a
and b
would, nonetheless, be undefined
, each. undefined + undefined
has semantics that don’t result in an error, but hopefully you get the point: in your original code, myFunction()()
would throw an error only because undefined(firstFunction())
isn’t possible. The culprit is the same: caller
is undefined
.