http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/
http://javascript.info/callbacks
Callbacks are reference to function that gets passed around like variables.
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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function functionFirst(callback) { console.log("--> functionFirst"); // 1 setTimeout(function() { console.log('Back at it again'); // after the 3 secondes, // 4 callback(); // 5 }, 3000); // the timeout here is to simulate an HTTP request or some long time operation console.log("<-- functionFirst"); // 2 } function functionSecond() { console.log('with the white Vans!'); // 8 } functionFirst(function(){ console.log("--> callback"); // 6 functionSecond(); // 7 console.log("<-- callback"); // 9 }); console.log('Damn Daniel'); // 3 |
So why is this useful? Async execution + callback
Say you send off an HTTP request and you need to do something with the response. Instead of holding up your browser, you can use a callback to handle the response whenever it arrives. In the code example above, the async execution simulated by setTimeout first counts to 3 seconds, then it executes the callback after the 3 seconds.
The only reason setTimeout is used is to simulate an async operation that takes a certain time. Such operations could be reading from a text file, downloading things or performing an HTTP request.
Callbacks are closures. 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.
Also notice there is no timeOut to simulate a HTTP request or a long standing operation, thus, it will process it as is.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
var allUserData = []; //empty array function Person () { // constructor var name = "Ha Dooo Ken"; // this is a closure, it refrences local, parameters, outer, and global variables this.logStuff = function (userData) { console.log(" logStuff <--"); console.log("-- global allUserData --"); console.log(allUserData); console.log("-- outer function property 'name' -- "); console.log(name); if (typeof userData === "object") { for (var key in userData) { console.log("item: " + userData[key]); } } console.log(" logStuff -->"); } } function getInput(options, callback) { console.log("getInput <--"); console.log(callback); allUserData.push(options); // push data onto the array console.log("executing callback(option)"); callback(options); // then throw the data into the callback console.log("getInput -->"); } console.log("--- Program Start ---\n"); getInput( {name: "Ricky", specialty: "js"}, new Person().logStuff ); console.log("\n--- Program End ---"); |
output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
--- Program Start --- getInput <-- [Function] executing callback(option) logStuff <-- -- global allUserData -- [ { name: 'Ricky', specialty: 'js' } ] -- outer property 'name' -- Ha Dooo Ken item: Ricky item: js logStuff --> getInput --> --- Program End --- |
JavaScript Event Loop
Ok, now we know what functions and callbacks are, let’s see how they get used. Let’s look at a very simple asynchronous callback example:
1 2 3 4 5 6 7 8 9 10 |
console.log('Before timeout'); setTimeout(function() { console.log('Timeout callback'); }, 0); console.log('After timeout'); // output: // Before timeout // After timeout // Timeout callback |
If you’re used to procedural programs, this may seem a bit strange. JavaScript is based around something called the event loop. The simple version is that JavaScript runs a loop, and on each iteration (or tick) of this loop, one event will be processed. This event could be a timeout completing, an IO operation returning, an incoming HTTP request, etc. To help with this, JavaScript utilizes a message queue.
Certain operations will insert a message into the queue (e.g. setTimeout), and if there is a handler registered for the message, it will be executed.
In our above example, on tick 1, we do the following:
Log “before timeout”
Add a message handler (the message will be added to the message queue sometime after your timeout of 0 seconds)
Log “after timeout”
Now at some point in the future, JavaScript will insert a message into the queue to call your timeout handler. This will happen on a future “tick”, and that’s why “Timeout callback” is called after the other logs.
Asynchronous operations
Since JavaScript is single threaded using an event loop, we wouldn’t want to do blocking operations (e.g. reading a file) in the main loop. This would stop the loop until the blocking operation finished, meaning none of our other code could run. While this is how many languages work in their default configuration (e.g. Ruby), JavaScript & Node.js were designed to be run as a single process using non blocking operations only.
callback references global variables also
Notice how our callback also has access the global variables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// global variable var generalLastName = "Tsao"; // definition of getInput function getInput(options, callback) { console.log("receiving data options, callback"); allUserData.push(options); // push data onto the array console.log(allUserData); // Make sure the callback is a function if (typeof callback === "function") { // callback references global variables also callback(generalLastName, options); } } |
More Examples
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
var gameOver = function() { console.log("You Win! Game Over"); } var tauntOpponent = function(quote) { setTimeout(function() { gameOver(); }, 2000); console.log(quote); } var hurricaneKick = function (power) { console.log("Tats Mak Sen Buuu Kyaku! psh psh psh psh"); setTimeout(function() { console.log("..opponent just lost 5X" + power + " life bars"); tauntOpponent("you must defeat sheng long to stand a chance"); }, 5000); console.log("you just executed hurricaneKick"); } let dragonPunch = function (powerLevel) { console.log("FIGHT!!"); console.log("Shoooo Ryue Ken .... !! Pppppppppsssshhhhh......"); setTimeout(function() { console.log("...opponent just lost " + powerLevel + " life bars"); hurricaneKick(20); }, 3000); console.log("you just executed dragonPunch"); } // invoking it () // as variable, do not invoke it function executeSpecialMove(power) { console.log("Round 1...."); setTimeout(dragonPunch, 2000, power); } executeSpecialMove(88); |