function inside of function runs with body
To answer your question we can analyze the code example. If we read the code from top to bottom and from left to right (note that even though that is how code is parsed, that is NOT how code is executed, but we will come back to that), we first see
function add(getX,getY,cb) {
var x, y;
// ...
}
This is a declaration for a function named add
, with 3 parameters named getX
, getY
and cb
.
The first line inside the function body is
var x, y;
A var
keyword inside a function body means this is a function-scoped variable declaration. This just means that x
and y
are declared from the start to the end of the add
function body (the var
keyword can also be used for globally-scoped variable declarations, there is also the let
keyword for block-scoped variable declaration).
The next code part is
getX( function(xVal){
x = xVal;
if (y != undefined) {
cb(x + y );
}
});
This starts with a call to getX(...);
(remember getX
is the first parameter of the add
function that we are in). So getX
has to be something that we can call, i.e. a function, otherwise we will get a runtime error.
The first (and only) input parameter that is passed to the getX
call is
function(xVal){
x = xVal;
if (y != undefined) {
cb(x + y );
}
}
which is a function expression.
The function keyword can be used to define a function inside an expression.
Using a function
keyword in this way (or using an arrow function expression) without specifying a name, defines an anonymous function. You might hear others refer to them (depending on context) as callbacks/lambdas/closures.
So this tells us that the getX
function should accept a function as the first parameter and potentially call it as part of its internal logic.
We also notice that this anonymous function function(xVal){ ... }
accepts a single parameter xVal
. This tells us that the getX
function should pass some value as the first parameter when (if) it calls this function.
So if the getX
function ever calls back this anonymous function that we are passing as its first parameter, then the body of this anonymous function will be executed. If the getX
function never calls it, then the body of this anonymous function will never be executed.
The body of the anonymous function is
x = xVal;
if (y != undefined) {
cb(x + y );
}
which just assigns xVal
to x
and conditionally calls cb(x + y)
, i.e. the third parameter of the add
function, which is still the outer function scope here.
We could do a similar breakdown for the getY
call. The only difference would be that it calls the getY
function parameter and the body of the anonymous function assigns yVal
to y (but it still conditionally calls cb(x + y)
).
The last few lines of the code example are
// fetchX() and fetch() are sync or async functions
add(fetchX, fetchY, function(sum) {
console.log(sum);
});
So this is a call of the previously declared named function function add(getX, getY, cb) { ... }
.
When this code line is executed it will call add
with
- 1st parameter:
fetchX
- 2nd parameter:
fetchY
- 3rd parameter: an anonymous function
function(sum) { console.log(sum); }
. This anonymous function accepts a single parametersum
and only when (if) called it logs it to the console.
- what is the point of passing in fetchX and fetchY?
As we see from the breakdown above, fetchX
and fetchY
are passed into the add
function call in order for them to serve as "replacements" for calls to getX
and getY
inside the function body. You can think of it that the call to getX(...)
calls fetchX(...)
and the call to getY(...)
calls fetchY(...)
.
They will be "called back" from inside the function, that's why in such cases we call them callbacks, they are functions passed as parameters into another function, and said other function might call them based on its internal logic. In your example they are called once each (assuming no errors happen during runtime).
- How does getX and getY run? I can imagine running w/ getX() and getY() but code has getX(){} and getY(){} and that looks more like function declaration?
As mentioned earlier the code is not always executed from top to bottom and from left to right. So after parsing the code from the example, the code interpreter will execute the add
call first (because the rest is just a declaration for the add
function) and it will pass fetchX
, fetchY
and function(sum){ console.log(sum); }
as the parameters. Right after the call happens the execution continues with the add
function body.
Right from the getX
call inside the add
function body there are different possible flows, depending on what the logic inside the getX
and getY
is (specifically fetchX
and fetchY
in your case). Since we don't have a documentation for fetchX
and fetchY
nor their source code, we cannot know how the code will execute from here on. But we can explore some of the possible flows.
I added a few console.log
statements to your original code example. In that way we can see the order of the execution. Another way to do this, without the need to add extra log statements, is to use the debugger, place a breakpoint somewhere inside the function and then use the step commands to step through the code as it executes.
Option 1: If both fetchX
and fetchY
call their first parameter (the callback they receive) always exactly once synchronously then the flow is (run the code snippet)
// These two callbacks could be defined elsewhere, maybe even in code not under our control, such as in a library...
fetchX = function(callback) {
callback(2); // unconditional single synchronous call
};
fetchY = function(callback) {
callback(3); // unconditional single synchronous call
};
console.log("Entering the code example scope");
function add(getX,getY,cb) {
console.log("Called: function add(getX,getY,cb)");
var x, y;
getX( function(xVal){
console.log("Called: function(xVal) xVal =", xVal);
x = xVal;
if (y != undefined) {
cb(x + y);
}
});
getY( function(yVal){
console.log("Called: function(yVal) yVal =", yVal);
y = yVal;
if (x != undefined) {
cb(x + y);
}
});
console.log("Returning from: function add(getX,getY,cb)");
}
add(fetchX, fetchY, function(sum) {
console.log("Called: function(sum)");
console.log(sum);
console.log("Returning from: function(sum)");
});
console.log("Leaving the code example scope");
Option 2: If fetchX
calls its callback always exactly once synchronously and fetchY
calls its callback parameter always exactly once asynchronously, then the flow is (run the code snippet)
fetchX = function(callback) {
callback(2); // unconditional single synchronous call
};
fetchY = function(callback) {
setTimeout(function() { callback(3); }, 0); // unconditional single asynchronous call
};
console.log("Entering the code example scope");
// add function is same as before, just in one line for space purposes
function add(getX,getY,cb) {
console.log("Called: function add(getX,getY,cb)");
var x, y; getX( function(xVal){ console.log("Called: function(xVal) xVal =", xVal); x = xVal; if (y != undefined) { cb(x + y); } }); getY( function(yVal){ console.log("Called: function(yVal) yVal =", yVal); y = yVal; if (x != undefined) { cb(x + y); } });
console.log("Returning from: function add(getX,getY,cb)");
}
add(fetchX, fetchY, function(sum) {
console.log("Called: function(sum)");
console.log(sum);
console.log("Returning from: function(sum)");
});
console.log("Leaving the code example scope");
Option 3: If fetchX
calls its callback always exactly once asynchronously and fetchY
calls its callback parameter always exactly once synchronously, then the flow is (run the code snippet)
fetchX = function(callback) {
setTimeout(function() { callback(2); }, 0); // unconditional single asynchronous call
};
fetchY = function(callback) {
callback(3); // unconditional single synchronous call
};
console.log("Entering the code example scope");
// add function is same as before, just in one line for space purposes
function add(getX,getY,cb) {
console.log("Called: function add(getX,getY,cb)");
var x, y; getX( function(xVal){ console.log("Called: function(xVal) xVal =", xVal); x = xVal; if (y != undefined) { cb(x + y); } }); getY( function(yVal){ console.log("Called: function(yVal) yVal =", yVal); y = yVal; if (x != undefined) { cb(x + y); } });
console.log("Returning from: function add(getX,getY,cb)");
}
add(fetchX, fetchY, function(sum) {
console.log("Called: function(sum)");
console.log(sum);
console.log("Returning from: function(sum)");
});
console.log("Leaving the code example scope");
Option 4: If both fetchX
and fetchY
call their callback always exactly once asynchronously and fetchX
calls it sooner, then the flow is (run the code snippet)
fetchX = function(callback) {
setTimeout(function() { callback(2); }, 0); // unconditional single asynchronous call
};
fetchY = function(callback) {
setTimeout(function() { callback(3); }, 50); // unconditional single asynchronous call (further delayed)
};
console.log("Entering the code example scope");
// add function is same as before, just in one line for space purposes
function add(getX,getY,cb) {
console.log("Called: function add(getX,getY,cb)");
var x, y; getX( function(xVal){ console.log("Called: function(xVal) xVal =", xVal); x = xVal; if (y != undefined) { cb(x + y); } }); getY( function(yVal){ console.log("Called: function(yVal) yVal =", yVal); y = yVal; if (x != undefined) { cb(x + y); } });
console.log("Returning from: function add(getX,getY,cb)");
}
add(fetchX, fetchY, function(sum) {
console.log("Called: function(sum)");
console.log(sum);
console.log("Returning from: function(sum)");
});
console.log("Leaving the code example scope");
Option 5: If both fetchX
and fetchY
call their callback always exactly once asynchronously and fetchY
calls it sooner, then the flow is (run the code snippet)
fetchX = function(callback) {
setTimeout(function() { callback(2); }, 50); // unconditional single asynchronous call (further delayed)
};
fetchY = function(callback) {
setTimeout(function() { callback(3); }, 0); // unconditional single asynchronous call
};
console.log("Entering the code example scope");
// add function is same as before, just in one line for space purposes
function add(getX,getY,cb) {
console.log("Called: function add(getX,getY,cb)");
var x, y; getX( function(xVal){ console.log("Called: function(xVal) xVal =", xVal); x = xVal; if (y != undefined) { cb(x + y); } }); getY( function(yVal){ console.log("Called: function(yVal) yVal =", yVal); y = yVal; if (x != undefined) { cb(x + y); } });
console.log("Returning from: function add(getX,getY,cb)");
}
add(fetchX, fetchY, function(sum) {
console.log("Called: function(sum)");
console.log(sum);
console.log("Returning from: function(sum)");
});
console.log("Leaving the code example scope");
Option 6: If fetchX
calls its callback exactly three times asynchronously on an interval of 30ms with values 10, 20, 30 and fetchY
calls its callback exactly three times asynchronously on an interval of 50ms with values 1, 2, 3, then the flow is (run the code snippet)
fetchX = function(callback) {
setTimeout(function() { callback(10); }, 30);
setTimeout(function() { callback(20); }, 60);
setTimeout(function() { callback(30); }, 90);
};
fetchY = function(callback) {
setTimeout(function() { callback(1); }, 50);
setTimeout(function() { callback(2); }, 100);
setTimeout(function() { callback(3); }, 150);
};
console.log("Entering the code example scope");
// add function is same as before, just in one line for space purposes
function add(getX,getY,cb) {
console.log("Called: function add(getX,getY,cb)");
var x, y; getX( function(xVal){ console.log("Called: function(xVal) xVal =", xVal); x = xVal; if (y != undefined) { cb(x + y); } }); getY( function(yVal){ console.log("Called: function(yVal) yVal =", yVal); y = yVal; if (x != undefined) { cb(x + y); } });
console.log("Returning from: function add(getX,getY,cb)");
}
add(fetchX, fetchY, function(sum) {
console.log("Called: function(sum)");
console.log(sum);
console.log("Returning from: function(sum)");
});
console.log("Leaving the code example scope");
Here are the outputs of the above options
Option 1:
Entering the code example scope
Called: function add(getX,getY,cb)
Called: function(xVal) xVal = 2
Called: function(yVal) yVal = 3
Called: function(sum)
5
Returning from: function(sum)
Returning from: function add(getX,getY,cb)
Leaving the code example scope
Option 2:
Entering the code example scope
Called: function add(getX,getY,cb)
Called: function(xVal) xVal = 2
Returning from: function add(getX,getY,cb)
Leaving the code example scope
Called: function(yVal) yVal = 3
Called: function(sum)
5
Returning from: function(sum)
Option 3:
Entering the code example scope
Called: function add(getX,getY,cb)
Called: function(yVal) yVal = 3
Returning from: function add(getX,getY,cb)
Leaving the code example scope
Called: function(xVal) xVal = 2
Called: function(sum)
5
Returning from: function(sum)
Option 4:
Entering the code example scope
Called: function add(getX,getY,cb)
Returning from: function add(getX,getY,cb)
Leaving the code example scope
Called: function(xVal) xVal = 2
Called: function(yVal) yVal = 3
Called: function(sum)
5
Returning from: function(sum)
Option 5:
Entering the code example scope
Called: function add(getX,getY,cb)
Returning from: function add(getX,getY,cb)
Leaving the code example scope
Called: function(yVal) yVal = 3
Called: function(xVal) xVal = 2
Called: function(sum)
5
Returning from: function(sum)
Option 6:
Entering the code example scope
Called: function add(getX,getY,cb)
Returning from: function add(getX,getY,cb)
Leaving the code example scope
Called: function(xVal) xVal = 10
Called: function(yVal) yVal = 1
Called: function(sum)
11
Returning from: function(sum)
Called: function(xVal) xVal = 20
Called: function(sum)
21
Returning from: function(sum)
Called: function(xVal) xVal = 30
Called: function(sum)
31
Returning from: function(sum)
Called: function(yVal) yVal = 2
Called: function(sum)
32
Returning from: function(sum)
Called: function(yVal) yVal = 3
Called: function(sum)
33
Returning from: function(sum)
There are of course more possible flows. Either of the functions could synchronously or asynchronously call its callback multiple times (not just a single time), or it could call it on an interval forever or it could call it conditionally - so sometimes not even once.
As you can see from all the possibilities above, without the documentation or source code for functions fetchX
and fetchY
, it is impossible to determine what the flow is. Also the flow will dynamically change based on what functions are passed in.
You can just copy any of the code snippets above and change the logic inside fetchX
and fetchY
and explore yourself other flows that you are interested in.