Skip to content

Latest commit

 

History

History
593 lines (476 loc) · 17.2 KB

README.md

File metadata and controls

593 lines (476 loc) · 17.2 KB

Advanced JavaScript

Table of Contents

  1. Functions
  2. Synchronous Functions
  3. Asynchronous Functions & Callbacks
  4. Async Library
  5. Promises

1.0 Functions

Let's look at a snippet of code:

for(var i = 0; i < 5; i++) {  
	setTimeout(function() {  
		console.log(i);  
	}, 1000);  
}

Let's run it in the javascript console and see what it produces.

What we want:

0
1  
2  
3  
4

In actuality it produces:

5  
5  
5  
5  
5

What the heck?

setTimeout is an asynchronous function.

asynchronous (adj.)

  1. not occurring at the same time.
  2. (of a computer or other electrical machine) having each operation started only after the preceding operation is completed.
  3. Computers, Telecommunications. of or relating to operation without the use of fixed time intervals (opposed to synchronous).

That doesn't really help.

Essentially, asynchronous means a function takes unpredictable time to complete. So in our example, the for loop executed and completed before the setTimeout function even began. We need to wait for the console.log before we increment the i. How can we fix that? Before we tackle this problem, let's review what synchronous functions are.

1.1 Synchronous Functions

How are functions normally defined? How do we return values?

Here's one way:

function hello(param) {
	// do something
	// optionally return something
	return "hello!";
}

Here's another way:

var hello = function(param) {
	return "hello!";
}

How do we call functions?

hello("this is a string");
hello();

Fun fact: The following function is an anonymous function. It is not named.

function() {
	// do something
	console.log("hello world");
}

Ok. So how do asynchronous functions differ from synchronous ones?

1.2 Asynchronous Functions & Callbacks

It’s key to understand how javascript executes code. Let’s take a look at our first example. Line numbers have been added.

1. 	for(var i = 0; i < 5; i++) {
2. 		setTimeout(function() { 
3. 			console.log(i); 
4.		}, 1000);
5.	}

So first it’ll hit line 1. i is initially defined as 0, and it’ll start the loop. Next it’ll hit line 2 and wait a second (1000 milliseconds).

Since setTimeout is an asynchronous function, we’re actually passing in to it an anonymous function which is defined inline. That function is:

function() {
	console.log(i);
}

This code can also be written without an anonymous function. We'll name the previous anonymous function printSomething.

function printSomething() { 
	console.log(i); 
}
setTimeout(printSomething, 1000);

Exactly! We aren't calling printSomething, we're only defining it above, and passing it into setTimeout as a param. setTimeout will call the function.

So let's continue with the code execution. Once it hits line 2, it'll call the setTimeout function and skip all the way to line 5. And since it's a for loop, it'll increment i and return to line 2. It'll keep repeating until i == 5.

So who executes our anonymous function (or the printSomething function)? setTimeout does. Although it's difficult to see in the native code, essentially when setTimeout completes (it waits 1000 ms), it will call the function we just passed in.

Our anonymous function then, is called a callback function.

So let’s look at another example of a callback function. This time we’ll add some jQuery too:

$( "#target" ).click(function() {
	alert("Handler for .click() called");
});

In this snippet of code, when you click on an element with id "target", it will alert "Handler for .click() called".

Another example of a callback function is the jQuery GET request:

$.get("http://www.google.com", function(data) {
	console.log(data);
});

Once it completes the get request, it'll call our anonymous function, which will console.log the data returned from the request.

Now let's make our own callback function. Let’s say we want to make a function that retrieves data from an API, modifies the data, and returns it.

Here's the wrong way to do it:

function getIP() {
	$.get("http://ip.jsontest.com", function(data) {
		var result = data.ip;
		return result;
	});
}

If we were to try it out, we'd see that it returns undefined. That’s because $.get is asynchronous, so let's try again using a callback function.

function getIP(callback) {
	$.get("http://ip.jsontest.com", function(data) {
		var result = data.ip;
		callback(result);
	});
}

To call this function:

getIP(function(ip) {
	console.log(ip);
});

Output: 127.0.0.1

Cool!

There are some benefits to using asynchronous functions. To demonstrate this, we'll build an app that retrieves data from an API and displays it to the user.

Let's begin with a basic HTML page:

<html>
<script></script>
<body>
</body>
</html>

Now let's make a request to the API and display the data:

<html>
<script>
// when the page is loaded
$( document ).ready(function() {
	$.get("https://github.com/gitapi/repos/octokit/octokit.rb", function(data) {
		$("#github-repo-name").text(data.full_name + " – " + data.description);
		$("#github-repo-url").text(data.html_url);
		$("#github-repo-url").attr("href", data.html_url);
	});
});
</script>
<body>
	<p id="github-repo-name"></p>
	<a id="github-repo-url"></a>
</body>
</html>

Now let's show a loading gif, and then hide it when the data appears:

<html>
<script>
	// when the page is loaded
	$( document ).ready(function() {
		$.get("https://github.com/gitapi/repos/octokit/octokit.rb", function(data) {
			$("#loading-gif").hide();
			$("#github-repo-name").text(data.full_name + " – " + data.description);
			$("#github-repo-url").text(data.html_url);
			$("#github-repo-url").attr("href", data.html_url);
		});
	});
</script>
<body>
	<img id="loading-gif" src="http://www.ajaxload.info/cache/FF/FF/FF/00/00/00/1-0.gif">
	<p id="github-repo-name"></p>
	<a id="github-repo-url"></a>
</body>
</html>

To emphasize this loading effect, we can add a small setTimeout:

// when the page is loaded
<html>
<script>
	$( document ).ready(function() {
    		$.get("https://github.com/gitapi/repos/octokit/octokit.rb", function(data) {
        		setTimeout(function(){ 
            			$("#loading-gif").hide();
            			$("#github-repo-name").text(data.full_name + " – " + data.description);
            			$("#github-repo-url").text(data.html_url);
            			$("#github-repo-url").attr("href", data.html_url);
            		}, 1000);
        	});
    	});
</script>
<body>
	<img id="loading-gif" src="http://www.ajaxload.info/cache/FF/FF/FF/00/00/00/1-0.gif">
	<p id="github-repo-name"></p>
	<a id="github-repo-url"></a>
</body>
</html>

So as you can see, callbacks are definitely useful. However, one problem that exists is callback hell.

Code tends to shift rightwards instead of downwards: Source: callbackhell.com

fs.readdir(source, function(err, files) {
	if (err) {
		console.log('Error finding files: ' + err)
	} else {
		files.forEach(function(filename, fileIndex) {
			console.log(filename)
			gm(source + filename).size(function(err, values) {
				if (err) {
					console.log('Error identifying file size: ' + err)
				} else {
					console.log(filename + ' : ' + values)
					aspect = (values.width / values.height)
					widths.forEach(function(width, widthIndex) {
						height = Math.round(width / aspect)
						console.log('resizing ' + filename + 'to ' + height + 'x' + height)
						this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
							if (err) console.log('Error writing file: ' + err)
						})
					}.bind(this))
				}
			})
		})
	}
})

There are a few ways to get around this. One way is using the async library.

2.0 Async Library

https://github.com/caolan/async

This library provides various functions that allow you to easily interact with asynchronous functions. For example, this library can help use create a simple async for loop (as we saw in our example earlier).

The function async.eachSeries iterates through an array. The beauty is that our asynchronous function waits until it finishes before going onto the next element in the array. So let's see how we'd implement it with our first example.

async.eachSeries([0,1,2,3,4,5], function(i, callback) {
    setTimeout(function(){ 
        console.log(i); 
        callback();
    }, 1000);
}, function(err){
    console.log('All files have been processed successfully');
});

http://jsfiddle.net/mLgzx196/7/

As we can see, once the setTimeout function finishes, we call the callback, which triggers the next iteration. And check out the console. Each second, it prints out the integer. Sweet!

Now one more thing. Let’s say we want to run all the functions at the same time, but preserve the variables. So with our example, after 1 second, it’d print 1,2,3,4,5.

Well, async has another awesome function called each, which will call each of the functions immediately, without waiting for them to finish, but still preserve the callback functions associated. Let's take a look at our example.

async.each([0,1,2,3,4,5], function(i, callback) {
    setTimeout(function(){ 
        console.log(i); 
        callback();
    }, 5000);
}, function(err){
    console.log('All files have been processed successfully');
});

http://jsfiddle.net/mLgzx196/8/

Now let's take a look at a real life example of how we might use the async library.

async.each([0,1,2,3,4,5], function(i, callback) {
    $.get("http://echo.jsontest.com/" + i + "/" + i*i, function(data) {
        console.log(data); 
        callback();
    })
}, function(err){
    console.log('All files have been processed successfully');
});

http://jsfiddle.net/mLgzx196/9/

We’re calling a get request for each i, which returns an object. Now what’s so cool is that javascript calls these async functions, but preserves the variables, as well as the callback functions for each get request. What that means is that’ll console log the correct data for each request - check out the console. Note: it doesn’t preserve the order because of the nature of http requests, the server we’re pinging, etc (contrary to the previous example).

Object {5: "25"}
Object {3: "9"}
Object {4: "16"}
Object {1: "1"}
Object {2: "4"}
Object {0: "0"}

So it iterates through the array and prints the objects we sent over in the http requests correctly. Super useful function over here.

We can also use promises to interact with asynchronous functions.

3.0 Promises

What is a promise?

"A promise is an object that represents the return value or the thrown exception that the function may eventually provide." Source: https://github.com/kriskowal/q

For the purposes of this talk, we are going to use the Q library.

Promises have 3 key properties:

  1. then() - this function is called after the promise resolves. Using this function, we can chain together asynchronous functions seamlessly with promises.
  2. resolve() - the promise succeeds and returns a value.
  3. reject() - the promise fails and returns an error.

How to use a promise with an asynchronous function:

  1. We first obtain a promise object by calling Q.defer().
  2. We do some asynchronous processes (e.g. HTTP GET request).
  3. We return the promise at the end of the function.
  4. After the asynchronous processes finish, we either resolve() or reject() the promise. We do this generally in the callback function of the asynchronous function.

Note you can also create a promise using Q.fcall

In order to demonstrate how to do this, we'll build an app that makes 3 GET requests using promises.

http://jsfiddle.net/mLgzx196/

// src:  https://gist.github.com/jeffcogswell/8257755
 
function one() {
    var deferred = Q.defer();
    console.log("Starting one's ajax");
    $.ajax( {
        url: 'http://ip.jsontest.com/',
        success: function() {
            console.log('Finished with one. Ready to call next.');
            deferred.resolve();
            console.log("hello");
            console.log(deferred);
        }
    });
    return deferred.promise;
}
 
function two() {
    var deferred = Q.defer();
    console.log("Starting two's ajax");
    $.ajax( {
        url: 'http://ip.jsontest.com/',
        success: function() {
            console.log('Finished with two. Ready to call next.');
            deferred.resolve();
        }
    });
    return deferred.promise;
}
 
function three() {
    var deferred = Q.defer();
    console.log("Starting three's ajax");
    $.ajax( {
        url: 'http://ip.jsontest.com/',
        success: function() {
            console.log('Finished with three. Ready to call next if there is one.');
            deferred.resolve();
        }
    });
    return deferred.promise;
}
 
one()
    .then(two)
    .then(three)
    .done();

And let's check out the console:

Starting one's ajax
Finished with one. Ready to call next.
Starting two's ajax
Finished with two. Ready to call next.
Starting three's ajax
Finished with three. Ready to call next if there is one.

As we can see, we were able to call the GET requests in order. We chain each function using the then.

We can also reject promises. Let's check out an example.

function one() {
    var deferred = Q.defer();
    console.log("Starting one's ajax");
    $.ajax( {
        url: 'http://ip.jsontest.com/',
        success: function() {
            console.log('Finished with one. Ready to call next.');
            deferred.resolve();
        }
    });
    return deferred.promise;
}
 
function two() {
    var deferred = Q.defer();
    console.log("Starting two's ajax");
    $.ajax( {
        url: 'http://ip.jsontest.com/',
        success: function() {
            console.log('Finished with two. Ready to call next.');
            deferred.resolve();
        }
    });
    return deferred.promise;
}
 
function three() {
    var deferred = Q.defer();
    console.log("Starting three's ajax");
    $.ajax( {
        url: 'http://ip.jsontest.com/',
        success: function() {
            console.log('Finished with three. Ready to call next if there is one.');
            deferred.resolve();
        }
    });
    return deferred.promise;
}
 
one()
    .then(two)
    .then(three)
    .then(function () {
        var deferred = Q.defer();
        setTimeout(function () {
            deferred.reject(new Error("HELP! :("));
        }, 1000);
        return deferred.promise;
    })
    .then(function () {
        console.log("hey!? where am i?")
    })
    .catch(function (error) {
        console.log('oh no'); 
        console.log(error);
    })
    .done();

http://jsfiddle.net/mLgzx196/1/

So we see in the newly chained function, we have the same format as the previous functions but we instead reject the promise. Check out the console!

Starting one's ajax
Finished with one. Ready to call next.
Starting two's ajax
Finished with two. Ready to call next.
Starting three's ajax
Finished with three. Ready to call next if there is one.
oh no
Error: HELP! :(

So as you can see, when we reject the promise, it skips over the chained function and jumps straight to the catch function. And we were successfully able to pass the error as well. Awesome.

And here's how'd we use promises in a for loop.

function promiseWhile(condition, body) {
    var done = Q.defer();
    function loop() {
        // When the result of calling `condition` is no longer true, we are
        // done.
        if (!condition()) return done.resolve();
        // Use `when`, in case `body` does not return a promise.
        // When it completes loop again otherwise, if it fails, reject the
        // done promise
        body().then(loop);
    }
    // Start running the loop in the next tick so that this function is
    // completely async. It would be unexpected if `body` was called
    // synchronously the first time.
    loop();
    // The promise
    return done.promise;
}

// Usage
i = 0
promiseWhile(function () { return i < 5; }, function () {
    console.log(i);
    i++;
    var deferred = Q.defer();
    setTimeout(function () {
        deferred.resolve(new Error("HELP! :("));
    }, 1000);
    return deferred.promise;
}).then(function () {
    console.log("done");
}).done();

http://jsfiddle.net/mLgzx196/5/

Ok. Let’s look at the second part first. While the condition i < 5 is true, we run our function. And there’s our classic familiar setTimeout.

Now let’s check out the promiseWhile function. We have the same template with the deferred promises, and when the condition is met, we resolve the promise. Cool.

And we link the body function (which is pretty much a callback function) to the loop function. And there we have it, an async for loop using promises. Give yourselves a pat on the back.