As our programs start relying on user input/behavior and data that might not be available right away, we need to increasingly start thinking about how we can run our code at different times.
We refer to this "now and later" programming as asynchronous programming.
We have already used asynchronous programming in our code in previous lessons:
Although we have used asynchronous programming in our code, we have not discussed how this is all happening. Before we can truly understand how asynchronous programming works in JavaScript, we need to take a deeper look at functions and scope.
We have worked with numerous scenarios where we have run a block of code after a user has performed an action.
var $thingList = $('#fav-list');
$thingList.on('mouseleave', 'li', function(e) {
...
});
Taking a close look at the jQuery on() method, we notice two very important overlooked concepts:
They can be used in any part of the code that strings, arrays, or data of any other type can be used
This means we can store functions as variables, pass them as arguments to other functions, return them from other functions, or just run them ad-hoc without the need to assign them to anything.
setTimeout( function(){
console.log("Hello world");
}, 1000 );
A function that takes another function as an argument, or returns a function, is called a higher-order function.
var blastOff = function() {
console.log("Blasting off!");
}
function launchRocket(rocketName, blastOffCallback) {
console.log("Launching " + rocketName);
console.log("3... 2... 1...");
blastOffCallback();
}
launchRocket("Viking", blastOff);
// => Launching Viking
// => 3... 2... 1...
// => Blasting off!
In this example, the function being passed into launchRocket (blastOff) is acting as a "callback", which is a function that is designed to be executed by the code of another function.
var blastOff = function(destination) {
console.log("Blasting off for " + destination + "!");
}
function launchRocket(rocketName, blastOffCallback) {
console.log("Launching " + rocketName);
console.log("3... 2... 1...");
blastOffCallback("Mars");
}
launchRocket("Viking", blastOff);
// => Launching Viking
// => 3... 2... 1...
// => Blasting off for Mars!
Callback functions can also take arguments, even though we don't specify the need for arguments when we're passing the callback function itself as a variable
var blastOff = function(destination) {
console.log("Blasting off for " + destination + "!");
}
function makeRocketLauncher(rocketName, blastOffCallback) {
return function() {
console.log("Launching " + rocketName);
console.log("3... 2... 1...");
blastOffCallback("Mars");
};
}
var launchViking = makeRocketLauncher("Viking", blastOff);
var launchMariner = makeRocketLauncher("Mariner", blastOff);
launchViking();
launchMariner();
// => Launching Viking
// => 3... 2... 1...
// => Blasting off for Mars!
// => Launching Mariner
// => 3... 2... 1...
// => Blasting off for Mars!
Just as we can pass functions as arguments to other functions, we can also return functions from other functions:
Open the main.js file.
Test your code by running it in the command line using node!
Function being passed as callback argument
setTimeout( function(){
console.log("Hello world");
}, 1000 );
The function being passed into setTimeout() above is called an anonymous function expression because it is not named in any way -- it is not a function expression that got assigned to a variable name, and neither is it a function that got its name in a function declaration
Note that you don't have to pass anonymous functions as callbacks in cases like this -- you can pass named functions as well, no matter how they got their names:
function sayHi() {
console.log("Hello world");
}
var sayBye = function() {
console.log("Goodbye world");
}
setTimeout(sayHi, 1000 );
setTimeout(sayBye, 1261440000000);
// (1,261,440,000,000 milliseconds is 40 years)
Note: Anonymous functions are great, but they may give you some trouble when debugging, if you're looking at a stack trace and it shows a big column of unnamed functions. To get around this you can name them as shown above, or you can use a third way to give them a name which will show up in debugging, called a "named function expression":
setTimeout(function timer(){
console.log( "Hello world" );
}, 1000 );
We have the ability to execute our function expressions as soon as they are declared. This pattern is very commonplace: Immediately-invoked function expressions (IIFE).
We are familiar with this syntax:
var countDown = function() {
var counter;
for(counter = 3; counter > 0; counter--) {
console.log(counter);
}
}
var countDown = function() {
var counter;
for(counter = 3; counter > 0; counter--) {
console.log(counter);
}
}();
To make an IIFE and execute the function expression immediately adding parenthesis at the end of the expression:
IIFEs are just functions, so we can also pass arguments to them:
var counterStartPoint = 3;
(function countDown( global ){
var counter;
for(counter = global.counterStartPoint; counter > 0; counter--) {
console.log(counter);
}
})( window );
console.log( counterStartPoint ); // 3
Open the main.js file.
Callbacks and closures are the bread and butter of asynchronous programming. Looking back at our DOM and APIs lessons, our interfaces update on user interactions and/or once we receive data from remote locations. Best practices in JS call for these reactions to be handled in the form of callbacks.
Callbacks, closures, and IIFEs allow us to better organize our code for each scenario, as well as make our functions significantly more dynamic.
Sign up for a 500px developer account. Review the set of sign up instructions to help.
Optional homework: Students who want an additional challenge can try to complete this series of functional-programming JavaScript exercises. Some of these exercises use methods and syntax that we haven't covered in class and which you will likely need to look up on your own.