When we pass a callback function as an argument to another function, we are only passing the function definition. We are not executing the function in the parameter. In other words, we aren’t passing the function with the trailing pair of executing parenthesis () like we do when we are executing a function.
And since the containing function has the callback function in its parameter as a function definition, it can execute the callback anytime.
Note that the callback function is not executed immediately. It is “called back” (hence the name) at some specified point inside the containing function’s body.
Callback Functions Are Closures
When we pass a callback function as an argument to another function, the callback is executed at some point inside the containing function’s body just as if the callback were defined in the containing function. This means the callback is a closure. As we know, closures have access to the containing function’s scope, so the callback function can access the containing functions’ variables, and even the variables from the global scope.
Basic Principles when Implementing Callback Functions
1> Use Named OR Anonymous Functions as Callbacks - Here's an example of named callback function
In most use case of Node/Express ecosystem, we use anonymous functions that were defined in the parameter of the containing function. That is one of the common patterns for using callback functions. Another popular pattern is to declare a named function and pass the name of that function to the parameter. Consider this:
// global variable
var allUserData = [];
// generic logStuff function that prints to console - this will be used as a callback function
function logStuff(userData) {
if (typeof userData === "string") {
console.log(userData);
} else if (typeof userData === "object") {
for (var item in userData) {
console.log(item + ": " + userData[item]);
}
}
}
// A function that takes two parameters, the last one a callback function
function getInput(options, callback) {
allUserData.push(options);
callback(options);
}
// When we call the getInput function, we pass logStuff as a parameter.
// So logStuff will be the function that will called back (or executed) inside the getInput function
getInput({ name: "Rich", speciality: "JavaScript" }, logStuff);
// name: Rich
// speciality: JavaScript
Make Sure Callback is a Function Before Executing It
It is always wise to check that the callback function passed in the parameter is indeed a function before calling it. Also, it is good practice to make the callback function optional.
Let’s refactor the getInput function from the previous example to ensure these checks are in place.
function getInput(options, callback) {
allUserData.push(options);
// Make sure the callback is a function
if (typeof callback === "function") {
// Call it, since we have confirmed it is callable
callback(options);
}
}
Without the check in place, if the getInput function is called either without the callback function as a parameter or in place of a function a non-function is passed, our code will result in a runtime error.
Note the following ways we frequently use callback functions in JavaScript, especially in modern web application development, in libraries, and in frameworks:
For asynchronous execution (such as reading files, and making HTTP requests)
- In Event Listeners/Handlers
- In setTimeout and setInterval methods
- For Generalization: code conciseness
Example -1 super simple example of custom callback. done() is a callback which I am definining seperately.
done = () => {
console.log("Done from Callback");
};
printNum = (num, callback) => {
for (let i = 0; i <= num; i++) {
console.log(i);
}
if (callback && typeof callback === "function") {
callback();
}
};
printNum(5, done);
Example-2, where, I will pass the callback after invoking the function, without defining the callback separately
mySandwitch = (a, b, callback) => {
console.log(`Started eating my sandwitch.\n\nIt has: ${a} and ${b}`);
callback();
};
/*mySandwitch('cheese', 'ham', () => {
console.log('Finished eating my sandwitch');
})*/
// To make the callback optional, we can just do this:
mySandwitchOptional = (a, b, callback) => {
console.log(`Started eating my sandwitch.\n\nIt has: ${a} and ${b}`);
if (callback && typeof callback === "function") {
callback();
}
};
Write a custom callback function which returns the addition of its 2 arguments through a callback. That, the function would take 2 arguments and the third agument would be a callback function. And the final return output of that function would be with callback. - Was asked in Techolution interview */
addTwoArgs = (a, b, callback) => {
if (callback && typeof callback === "function") {
return callback();
}
};
let result = addTwoArgs(2, 5, () => {
return 2 + 5;
});
console.log(result);
Further Reading
1> https://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/
Basic concepts are pure functions, side effects, data mutation and declarative over imperative.
On top of that currying, compose and function composition are important concepts.
Knowledge of libraries like RamdaJS provide useful utilities build applications in a more functional way. Functions being a first class citizen make it possible for JS to be really functional is important as well.
As a side note - Redux need reducers to be “pure functions
Basic example of mutations
let state = {
wardens: 900,
animals: 800
};
This above Object holds the information of a Zoo application. If we change the number of animals in the state Object:
let state = {
wardens: 900,
animals: 800
};
state.animals = 90;
Our state object will hold/encode a new information:
state = {
wardens: 900,
animals: 90
};
This is called mutation.
Immutability comes when we want to preserve our state. To keep our state from changing we have to create a new instance of our state objects.
function bad(state) {
state.prp = "yes";
return state;
}
function good(state) {
let newState = { ...state };
newState.prp = "yes";
return newState;
}
Immutability makes our app state predictable, ups the performance rate of our apps and to easily track changes in state.
Pure Functions, Side Effects
Pure functions are functions that accept an input and returns a value without modifying any data outside its scope(Side Effects). Its output or return value must depend on the input/arguments and pure functions must return a value.
function impure(arg) {
finalR.s = 90;
return arg * finalR.s;
}
The above function is not a pure function because it modified a state finalR.s outside its scope.
function impure(arg) {
let f = finalR.s _ arg
}
The above function also isn’t a pure function because it didn’t return a value though it didn’t modify any external state.
function impure(arg) {
return finalR.s _ 3
}
The above function is impure, though it didn’t affect any external state, its output return finalR.s _ 3 isn't dependent on the input arg. Not only must pure function return a value but it must depend on the input.
function pure(arg) {
return arg \_ 4
}
The above is a pure function. It didn’t side effect any external state and it returns an output based on the input.
1. A pure function is deterministic. This means, that given the same input, the function will always return the same output. To illustrate this as a function in mathematical terms it is a well defined function. Every input returns a single output, every single time.
A pure function
const add = (x, y) => x + y
// A pure function
add is a pure function because it’s output is solely dependent on the arguments it receives. Therefore, given the same values, it will always produce the same output.
2. A pure function will not cause side effects. A side effect is any change in the system that is observable to the outside world.
const calculateBill = (sumOfCart, tax) => sumOfCart * tax
Is calculateBill pure? Definitely :) It exhibits the two necessary characteristics:
The function depends only on its arguments to produce a result The function does not cause any side effects The Mostly Adequate Guide states that side effects include, but are not limited to:
changing the file system inserting a record into a database making an http call mutations printing to the screen / logging obtaining user input querying the DOM accessing system state
Further Reading
https://hackernoon.com/javascript-and-functional-programming-pt-3-pure-functions-d572bb52e21c
Comments