Using jQuery Deferred Objects

PluralSight: Advanced Techniques in JavaScript and jQuery

This module promises to be philosophically difficult, and I can only hope it’s the key to learning a subject I’ve found infinitely difficult to comprehend. It’s a bit disconcerting that the tutorial begins by calling Deferred “a little understood method”—which I really hope the author means as “a little, understood method” rather than “a little-understood method.” !!!

1.1 Outline

PluralSight: Advanced Techniques in JavaScript and jQuery

Deferred is an alternative to using callbacks. You’d use it in asynchronous processing. It’s used already in jQuery for AJAX-related things.

1.2 Promise

PluralSight: Advanced Techniques in JavaScript and jQuery

When you make an AJAX call in jQuery, it returns an object that implements the JavaScript promise. (huh?) This is actually part of JavaScript, not jQuery.

Essentially, rather than making the AJAX call, waiting for whatever it’s going to return, and then doing stuff with that data…you say “I promise there’s going to be data in the future” and then you skip off and continue processing other code while the AJAX call is doing its thing. Eventually, the AJAX call comes on back and is like “hi. I’m here.” And since you keep your promises, you run on back to the calling code and execute some event handler.

There’s actually another step. When the AJAX call comes back and says “hi. I’m here”, it says more than that…it also says “So, I did it.” Or it says “Things didn’t work out.” If things went smoothly, then the promise is resolved. If things don’t go smooth, then the promise is rejected (and you get your legs broken).

Developers generally ignore the promise, and instead just use the success or failure callbacks (“I did it” / “things didn’t work out”) to implement functions. Well, why not?

1.3 Traditional AJAX Processing

PluralSight: Advanced Techniques in JavaScript and jQuery

Let’s look at an example, finally, because I don’t know what’s going on really.

$.get('somefile.html', function(result){
  // here’s the callback function
  // it processes the results and then
  // somehow indicates success or failure
});

result is the contents of that file.

This is how developers do it. They get the file and use the callback to make other processing happen. Tutorial says that it gets problematic if we need to execute multiple processes. How so? I dunno. Doesn’t say. Wouldn’t you just make a bunch of other functions that the callback would call? YES, says the tutorial. But this would mean that the processes are tightly coupled. How? I’m not sure. It says that the callback function would need to be aware of all the functions that need to execute—okay, but what if it didn’t? What if it was only aware of a single function and that function called all the others? Then wouldn’t there be a middleman that we could edit easily? Ugh!

1.4 Layout

PluralSight: Advanced Techniques in JavaScript and jQuery

So here’s our practice. The goal is that the user clicks a button, and at that point three different divs get loaded up with content asynchronously, and once that content is loaded, some other element becomes enabled. Here’s the basic HTML, CSS, and JavaScript we’ll be using, which I’m tucking all into a single file because I don’t feel like bothering with more files than I have to since I can’t do this on Codepen since you can’t have multiple files on that platform, and I can’t just load up a file from some other website because it’ll get denied for being on another domain. SO!

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style type="text/css">
        #Section1, #Section2, #Section3 {
          position: absolute;
          background-image: radial-gradient(30px 30px, #FFF, #EEF);
          width: 200px;
          height: 200px;
          border: 1px solid black;
          top: 40px;
          overflow-y: auto;
        }

        #Section1 {
          left: 10px;
        }

        #Section2 {
          left: 220px;
        }

        #Section3 {
          left: 430px;
        }

        #Proceed {
          position: absolute;
          width: 100px;
          top: 250px;
          left: 530px;
        }

        #Messages {
          position: absolute;
          width: 630px;
          height: 200px;
          top: 280px;
          left: 10px;
          overflow-y: auto;
        }
    </style>
</head>
<body>
  <input id="Load" type="Button" value="Load" />
  <div id="Section1"></div>
  <div id="Section2"></div>
  <div id="Section3"></div>
  <input id="Proceed" type="Button" value="Proceed" disabled="true" />
  <div id="Messages"></div>

  <a href="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js">https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js</a>


    $(function(){

        $('#Load').on('click',function(){

        });

    })

</body>
</html>

Also, I made a file called content1.html and this is what’s in it:

<div id="Content1">
 This is Content #1
</div>

1.5 Section 1 Content

PluralSight: Advanced Techniques in JavaScript and jQuery

The first way we’ll load content into section 1 will be using the load method.

$('#Load').on('click',function(){
            $("#Section1").load("Content1.html");
        });

AND…fail. Because of cross-origin requests, still. So what do we do? Fortunately, I have MAMP on my machine and I can load up the page on localhost and it works. Hooray! That’s pretty easy. So it loads the external html into the div when we click the button. But what it doesn’t do is enable the proceed button.

The tutorial says that .load() accepts a callback parameter. So, I’ll make my best guess on how to use it.

$("#Section1").load("Content1.html", $('#Proceed').prop('disabled', false));

All set. I know JavaScript. But they did it different:

$("#Section1").load("Content1.html", function(){
                $('#Proceed').removeAttr('disabled');
            });

This is great! Why do I need anything else? The questions raised are: once we begin importing content, where should we put the callback function?—that is, after loading which content div?

1.6 Loading All Content

PluralSight: Advanced Techniques in JavaScript and jQuery

One of the points we’re supposed to learn is that when it comes to asynchronously loading content, we don’t know what order things will finish loading. How do we simulate that? We need a lot of content. Like how about this 95 page note-taking session! That’ll be Content2, and for Content3 we need a “well-formed HTML page” so I’ll rename another file in the directory. So now we’ll load those up serially.

$('#Load').on('click',function(){
            $("#Section1").load("Content1.html");
            $("#Section2").load("Content2.html");
            $("#Section3").load("Content3.html", function(){
                $('#Proceed').removeAttr('disabled');
            });
        });

Looks good to me. Working well. According to the tutorial, it’s not. It says to watch carefully and you’ll see the divs load at different points in time, and that the proceed button is enabled early. Maybe I can test this withconsole.logs…no, for whatever reason it always shows me that the button is disabled. Also, I’m only getting the button to disable about 50% of the time, if not less.

“Just the act of inserting tracing logic can slow things down enough to make them work as expected. When we remove the tracing logic, the strange behavior resumes.”

Really interesting stuff, because that’s what happened here for me. It wasn’t working, I added some console.logs and things began working. And I’ve definitely had that happen in the past. Lesson learned: if adding a console.log makes things work, then it’s not that you have a magic console.log, it’s that you have a timing problem.

So, let’s just say things weren’t working on my machine in the same way that they’re not supposed to be working. The tutorial suggests that what we need is a way to know when each of the divs are finished loading so we don’t have to decide which one to put the callback on. And then he clarifies that what we really, really need is to know when they’re all finished.

1.7 Common Pattern

PluralSight: Advanced Techniques in JavaScript and jQuery

Now he introduces a pattern. I can’t decide whether he likes it or not. He says it’s used basically because everyone settled on it and never tried to figure out another way. I’m looking at it right now and it looks like a Christmas tree. I’ve heard that those are bad. Let’s find out!

$('#Load').on('click',function(){
            $("#Section1").load("Content1.html",function(){
                $("#Section2").load("Content2.html",function(){
                    $("#Section3").load("Content3.html", function(){
                        $('#Proceed').removeAttr('disabled');
                    });
                }); 
            });
        });

Right, so sometimes it works for me, and sometimes it doesn’t. WTF!? It all comes down to div3. I can’t get it to do most things most of the time. But sometimes I can. For instance, sometimes I can do remove() on it, and sometimes I can load content1.html or content3.html into it, but sometimes I can’t. Sometimes it works with the button, sometimes I have to just enter

$("#Section3").remove()

or something like that into the console. Although, even when it displays nothing, I can find its innerHTML in the inspector shows that stuff is there! Again: WTF!?

Anyway the tutorial says that this is a Christmas tree and should be avoided. Also, it forces everything to load up synchronously, which means that it could all get stuck on content2 for a while, which we don’t want. Also, what if we wanted to keep expanding it—then we’d be in real trouble because eventually the code would be off-screen when we’re typing it.

1.8 Deferred Object

PluralSight: Advanced Techniques in JavaScript and jQuery

There’s another way getting loading content that isn’t load–it’s get. get uses this jQuery deferred thing, while load does not.

Here’s a fascinating way of thinking about things—the button click is an asynchronous thing. We don’t want things to process until the button has been clicked, and when the button is clicked, we want things to happen…but we have no idea when the button click will happen! It’s exactly the same as waiting for the content to load in all the divs—when it happens, we want that other button to become enabled, and until all the content is loaded we want it to be disabled. But we have no idea how long it’ll take the content to load.

“When something happens, do something else. That is the essence of the deferred method in jQuery.”

I think my Content2 content was making things load a bit funny. So I replaced it with the full text of Moby Dick. That’s enough text that it takes a while to load, and at the same time you can see the other divs loading their content at different times. So here’s the new code from the tutorial:

$('#Load').on('click',function(){
            $.when(
                $.get('Content1.html', function(result){
                    $('#Section1').html(result);
                }, 'html'),
                $.get('Content2.html', function(result){
                    $('#Section2').html(result);
                }, 'html'),
                $.get('Content3.html', function(result){
                    $('#Section3').html(result);
                }, 'html')
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
            });
        });

And it works—it even works correctly. Everything loads asynchronously, and the Proceed button remains disabled until all content is loaded. This all looks pretty cut and dry (except for one bit that I’ll mention in a second)—when these things happen, then do those other things. Easy. I just don’t know what the three parameters on get are about. The third, specifically. Ah—it’s the datatype, which defaults to Intelligent Guess if you don’t specify. Nice.

But what is when doing? It’s returning a deferred object that we can process later. What does that even mean!? As we discussed earlier, the deferred object makes the promise to notify the code that a process is finished, presumably the finishing of the gets within it. Chained to when is then, which uses the notification.

load doesn’t return deferred objects. get does. And when wraps them all up into a single deferred object. While in the previous example we had each bit of code shoved into another’s callback so that when one completed another began, now all three of these can run at their own rates and upon completion notify when, which notifies then.

Does this mean that methods that don’t return deferred objects, like load won’t work inside a when?

get still uses callbacks to actually load the content, and it’s not until that content is loaded that it’s like ‘I’m done!’ and control is passed onward to the then.

load and get differ also in that load sticks the Content files into the divs automatically. Recall that it looked like this:

$("#Section1").load("Content1.html");

where we target the div we’d like the contents of the file to be loaded into. Compared to:

$.get('Content1.html', function(result){
                    $('#Section1').html(result);
                }, 'html'),

where we’re not calling the get method on a targeted element, but rather just calling it. It’s worth remembering that all these gets are actually being passed in as a bunch of a parameters to the when so that it’s kind of like

$.when( thisThingHappens, andThis, andMeToo ).then( haveParty );

Before we move on, of course I’m wondering what happens if we use load instead of get inside the when. Let me tell you.

$('#Load').on('click',function(){
            $.when(
                $("#Section1").load("Content1.html"),
                $("#Section2").load("Content2.html"),
                $("#Section3").load("Content3.html")

            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
console.log("i'm done!");
            });
        });

It still loads everything up, however the moment any one of the Content files are loaded, the then function processes. So, basically as soon as you click the button, the other button is enabled (and the console log print “I’m done!” — which I put in there because the Proceed button, although enabled, was not clickable until all three Contents were loaded. So I wasn’t entirely sure what its status was.).

Okay, so what about repeating code? There’s repeating code. How do we deal with that? This doesn’t work:

$(function(){
        function stuff(){
            for (var i = 1; i < 4; ++i){
                    $.get('Content' + i +'.html', function(result){
                        $('#Section' + i).html(result);
                    }, 'html')
                }
        }
        $('#Load').on('click',function(){
            $.when(
                stuff()
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
            });
        });
    });

I think it’s loading up the files (because I’ll get an error message if we try to load up Content0.html), and it’s certainly enabling the Proceed button—but it’s not loading up the content. I’m not going to much around with making this work since it’s not the correct way to do things anyway. What’s that? Yes, I’m fairly certain we’re about the learn the correct way. All my recent entries have been written with a nice mixture of creatine, espresso, and vitamins—but all I have with me today is espresso. I’m doing what I can to keep up the energy levels in here (here being this corporeal vessel).

1.9 LoadSection Function

PluralSight: Advanced Techniques in JavaScript and jQuery

So first he says we should write a function that accepts a single object parameter. Well, okay, that makes sense. Maybe I should try my bit of code again and edit it to at least take parameters. I can think of two ways to do that—but one way isn’t much different than the for loop.

function stuff(i){
            $.get('Content' + i +'.html', function(result){
                $('#Section' + i).html(result);
            }, 'html')
        }
        $('#Load').on('click',function(){
            $.when(
                stuff(1),
                stuff(2),
                stuff(3)
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
                console.log("me doney!")
            });
        });

This loads the content properly, but it processes then as soon as a single Content is loaded. I can only assume that the other way I thought of doing it, which is to just pass in the names of the files and divs, would be no different. And I’m not going to bother to find out because I’m so certain of my assumption. Okay maybe I will. Because it says in the tutorial we’ll do two parameters for now. So…

function stuff(content, target){
            $.get(content +'.html', function(result){
                $('#' + target).html(result);
            }, 'html')
        }
        $('#Load').on('click',function(){
            $.when(
                stuff('Content1','Section1'),
                stuff('Content2','Section2'),
                stuff('Content3','Section3')
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
                console.log("me doney!")
            });
        });

No difference. So now we’ll follow the tutorial and its use of the loadStart function. It takes an options parameter. OH! This isn’t some jQuery method—it’s something invented for this tutorial. And before I continue with it, a glance at the code sure makes it seem like it’s a better-written version of what I just wrote. The main difference being that the function returns a whole $.get bunch of code rather than the results of it. If I did that in my current code, all I’d have to do is add a single word, return, right before $.get :

function stuff(content, target){
            return $.get(content +'.html', function(result){
                $('#' + target).html(result);
            }, 'html')
        }
        $('#Load').on('click',function(){
            $.when(
                stuff('Content1','Section1'),
                stuff('Content2','Section2'),
                stuff('Content3','Section3')
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
                console.log("me doney!")
            });
        });

And now it works perfectly! Hooray! Meantime, that’s not how the tutorial wants it written because it’s missing a bunch of safety checks, and doesn’t stick the properties into objects. So let’s do it their way:

var loadSection = function(options){
            if (typeof options !== 'object')
                options = {};
            options.selector = options.selector || '';
            options.url = options.url || '';
            return $.get(options.url, function(result){
                $(options.selector).html(result);
            }, 'html')
        }
        $('#Load').on('click',function(){
            $.when(
                loadSection({url: 'Content1.html', selector: '#Section1'}),
                loadSection({url: 'Content2.html', selector: '#Section2'}),
                loadSection({url: 'Content3.html', selector: '#Section3'})
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
                console.log("me doney!")
            });
        });

Whatevs. The tutorial now jumps into the great beyond: planning for when things go wrong. For instance, what if the URL is wrong?

1.10 Negative Testing

PluralSight: Advanced Techniques in JavaScript and jQuery

So, to answer the question from the previous section, regarding what happens if we use a bad URL, let’s say we use a bad URL for #Section1 — what happens is that Sections 2 and 3 load up, Section 1 does not, and the Proceed button is never enabled. Behind the scenes we’re getting a 404 error for URL not found, which isn’t a “JavaScript error”—if it was, then the remaining divs wouldn’t have loaded. A JavaScript error would be more like if I included a bunch of commas or something.

The when/then statement says that when these three divs get loaded with content, enable the button. But it doesn’t address how to handle a failure. The point is that it’s doing precisely what we’ve asked it to do. Sometimes I see functions where the arguments are (onSuccess, onFailure) or some such thing. That’s because the first argument is the function that will be performed if things go successfully, while the second argument is what will be performed if things fail! So here’s my little test:

var loadSection = function(options){
            if (typeof options !== 'object')
                options = {};
            options.selector = options.selector || '';
            options.url = options.url || '';
            return $.get(options.url, function(result){
                $(options.selector).html(result);
            }, 'html')
        }
        $('#Load').on('click',function(){
            $.when(
                loadSection({url: 'Content11.html', selector: '#Section1'}),
                loadSection({url: 'Content2.html', selector: '#Section2'}),
                loadSection({url: 'Content3.html', selector: '#Section3'})
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
                console.log("me doney!")
            },function(){
                console.log("I'M A FAILURE!");
            });
        });

And now when I run it, the same things happen, except that the console prints I’M A FAILURE for all to see. Their fancier version replaces the console.log with:

function(result){
                $('#Messages').append('Failure!<br/>')
                    .append('Result: ' + result.statusText + '<br />');
            }

That’s fine. It does what it’s supposed to.

Failure!
Result: Not Found

And what happens if we change a second URL to be incorrect? Same thing:

Failure!
Result: Not Found

Moving on, the tutorial says that using two functions for the then is confusing, particularly to someone who doesn’t know about the two parameters being success/failure (like me a few minutes ago). This makes the code more difficult to maintain. I have a feeling we’re about to see onSuccess and onFailure. Oh, nope. It’s a whole new thing: the .fail() method. Like this:

$.when(
                loadSection({url: 'Content11.html', selector: '#Section1'}),
                loadSection({url: 'Content21.html', selector: '#Section2'}),
                loadSection({url: 'Content3.html', selector: '#Section3'})
            )
            .then(function(){
                $('#Proceed').removeAttr('disabled');
                console.log("me doney!")
            })
            .fail(function(result){
                $('#Messages').append('Failure!<br/>')
                    .append('Result: ' + result.statusText + '<br />');
            });

Works the same but is more clear about what happens when. The tutorial lastly makes clear that then and fail process almost immediately, and that all they’re basically doing is setting up handlers for possible future events relating to the deferred object from when.

1.11 Deferred Methods

PluralSight: Advanced Techniques in JavaScript and jQuery

There’s a bunch of deferred methods. The first thing the tutorial says is that “a deferred object is just a wrapper that can be placed around asynchronous processing.” Before I get even another sentence into things, I want to dissect that one. So, when returns a deferred object. Which means when is returning an object inside which is asynchronous processing. Is this true? Yes—all three loadSections are going on within it. And isn’t each of those loadSections a deferred object too? Yes, because each one of those has an asynchronous thingie, being the gets is wrapped inside. It all feels pretty abstract to me though. So what I did was just rename the URLs so they’re correct, named the argument in the then portion and printed it via console.log–so I guess I can see the deferred object in all its glory.

What I got was an array with three elements.
0: the content from the URL.
1: “success”
2: an object filled with all the properties we’ve been discussing, for instance, keys like “fail”, “promise”, and “then”, as well as a “[[Scopes]]” inside which is a “Closure: Deferred” with a “deferred” object and “promise” object and a state marked “resolved”. I guess the point is that these different methods return an object that contains the processing as well as a bunch of new functions relating to the deferred-ness of it all.

1.11.1 Promise

Continuing to look at when, we could store the whole when method results to a variable that we could use later. Currently we haven’t done anything like this—what we did was chain handlers to the end of the when method, being the most common usage.

“…jQuery is expecting to manage the state of the object for us.”

This is a motif throughout this chapter, that we’re not allowing jQuery’s deferred functionality to fully blossom. He states that we could change the state of a deferred object, which could have a great effect on what happens next. There’s another method that’s not when, but similar—it’s promise, which 1) offers only a subset of the when, and 2) does not allow us to change the state of the deferred object. So, in short: both when and promise return deferred objects, and in when we can change the state of it, while in promise we can’t.

1.11.2 Then

The then function is where we can handle the deferred object completing processing. It takes up to three parameters, two of which we’ve seen (success and failure), and a new one: progress.

1.11.3 Fail And Done

Rather than parameters, we could also use individual methods for each one also, as we’ve already seen with .fail() For success, we’d use .done()’ – it would only execute if all the things we’re waiting for to process finish successfully. Likewise,fail` would execute otherwise—and it only will call once, regardless of how many failures it encounters. This is because so long as one failure occurs, the whole thing fails, so it’d be redundant.

1.11.4 Progress

.progress() is one you’d use if there’s a long-running process you want details on the progress of. This can be used for updating a progress bar, for instance!

1.11.5 Always

.always is one that would execute after the completion of processing, regardless of success or failure.

1.11.6 Resolve / Reject

This is how we can change the state of a deferred object: .resolve() and .reject(). resolve will change the object’s state to indicate success, while reject will change it to indicate failure. Now that’s pretty cool! But, as mentioned above, this is only possible on when-produced deferred objects, not on ones produced by promise.

For this reason, on the client side, we’d prefer using promise over when because we’d then know that changes couldn’t be made to its state. Actually, we should just use promise whenever possible because it’s safer. So let’s edit our code!

$.when(
                loadSection({url: 'Content1.html', selector: '#Section1'}),
                loadSection({url: 'Content2.html', selector: '#Section2'}),
                loadSection({url: 'Content3.html', selector: '#Section3'})
            )
            .promise()
            .done(function(){
                $('#Proceed').removeAttr('disabled');
                console.log("me doney!")
            })
            .fail(function(result){
                $('#Messages').append('Failure!<br/>')
                    .append('Result: ' + result.statusText + '<br />');
            });

Here’s the edits, using what we just learned. We execute promise on its own, which filters the deferred object returned by when so that nothing we do will change the state. From there, we chain to that the success and failure methods that previously had been callbacks.

You know, it sure looks to me like the Content is loading serially. The third isn’t loading before the second finishes. I’m having difficulty finding anything related to this, except that someone mentioned jQuery versions about some other issues, which reminded me that for about as long as I’ve known of jQuery, I’ve run into bugs where the only real issue seems to be that jQuery 3.x doesn’t work and the last version of jQuery 2, v2.2.4, does work. So I switched out the version, and now at least the Content is loading asynchronously.

Result: I’m not sure why, but the console.log line is executing as soon as the first content div loads. The Proceed button is enabling as it should. I undid this batch of changes, and it worked properly using then–so I’m not sure what the issue is! I began reading the documentation on what “breaking changes” there are between jQuery v2 and v3, which led me to look back at what version the tutorial is using: 1.7! Let’s try that.

No change. I do notice, however, that it doesn’t print “done” until after box 3 is filled. Anyway, here’s my StackOverflow question on it.

1.12 Deferred Methods, continued…

PluralSight: Advanced Techniques in JavaScript and jQuery

What’s a callback function?

It’s a piece of code that is passed as a parameter to a method. That code will be called upon completion of some processing accomplished by the method.

What’s the difference between this and the done and fail methods? done and fail aren’t just running after the completion of the processing—they’re registering handlers with the deferred object. They also have greatly expanded functionality. So great, in fact, that I don’t understand any of what he just mentioned about it—he hasn’t explained yet.

Next, he’s assigning the when portion to a variable. Wha!? He turns this:

$.when(
                loadSection({url: 'Content1.html', selector: '#Section1'}),
                loadSection({url: 'Content2.html', selector: '#Section2'}),
                loadSection({url: 'Content3.html', selector: '#Section3'})
            )

into this:

var myDefer = $.when(
                loadSection({url: 'Content1.html', selector: '#Section1'}),
                loadSection({url: 'Content2.html', selector: '#Section2'}),
                loadSection({url: 'Content3.html', selector: '#Section3'})
            )

Now, here’s a few other changes. We move the button-enabler to an always clause, empty out the done clause, and add a new done clause.

var loadSection = function(options){
            if (typeof options !== 'object')
                options = {};
            options.selector = options.selector || '';
            options.url = options.url || '';
            return $.get(options.url, function(result){
                $(options.selector).html(result);
                console.log(options.url)
            }, 'html')
        }
        $('#Load').on('click',function(){
            var myDefer = $.when(
                loadSection({url: 'Content1.html', selector: '#Section1'}),
                loadSection({url: 'Content2.html', selector: '#Section2'}),
                loadSection({url: 'Content3.html', selector: '#Section3'})
            )
            .promise()
            .done(function(){
            })
            .done(function(){
                $('#Messages').append('Second Done handler<br />');
            })
            .fail(function(result){
                $('#Messages').append('Failure!<br/>')
                    .append('Result: ' + result.statusText + '<br />');
                console.log("failed");
            })
            .always(function(){
                $('#Proceed').prop('disabled',false);
            });
        });

In case you were wondering, if you run the code right now, it’ll work as you hope it will. Now, he’ll show that at the end of this statement (? Is that what this long chain of things is?) we can add more processing, and then expand on the myDefer variable with another done method. Let’s see:

.always(function(){
                $('#Proceed').prop('disabled',false);
            });

            // ADDITIONAL PROCESSING STEPS WOULD BE HERE

            myDefer.done(function(){
                $('#Messages').append('additional done handler called!<br />');
            })

It works! Now he wants to show what happens if we attach a done handler function from inside another done handler function. What I’d expect would be that it doesn’t get attached until after the deferred object is already resolved, and at that point it would execute immediately. Why do it? I dunno. Let’s see…

            .done(function(){
                myDefer.done(function(){
                    $('#Messages').append('embedded done handler.');
                })
            })

It works. I don’t know why I care though. The only thing I can think of is that it’s a quick and easy check for whether or not some bit of processing has completed. You can just tack stuff on here and there that only applies if the processing if finished—if it is, then execute immediately, if it isn’t, then wait until it finishes processing.

1.13 Dynamic Pages

PluralSight: Advanced Techniques in JavaScript and jQuery

He begins by noting that we’ve loading pages dynamically, but that within those pages is only static content. Okay. That means it’s the same content each time we load the page and could even be cached by the browser (if I didn’t keep clearing the cache before reloads). The possible issue here is that he says we’re only monitoring whether or not content is loaded successfully, not whether or not scripts embedded in those pages have executed. He creates a new file called Content1a.html.

<div id="Content1">
 This is Content #1
</div>


    $(function(){
        setTimeout(function(){
            $('#Content1')
                .append('<hr />')
                .append('more content loaded dynamically');
        }, 2000);
    });

Of course, we switch Content1.html to the new URL and then run it. It actually loads up at nearly the same moment as Content2, which makes it difficult to tell if the loading of the two is somehow connected. For instance, perhaps dynamic scripts don’t begin running until content has finished loading? Wouldn’t that be against the idea of asynchronous processing? Well, so let’s switch it out to 10 seconds delay. Now it loads up significantly later than #2. Great! So, everything works as expected? Not so fast. I just switched it to a 100ms delay instead. And now it doesn’t load up until #2 loads up. Actually, at the exact same moment. Well—I’m confused again. Let’s hope this is what happens in the tutorial also.

Who knows. Because that’s not where his head’s at. What he focuses on instead is that, as best seen when the delay is 10s, once the static content has loaded up in all three divs, but we’re still waiting for the 10s delayed dynamic content, the Proceed button is already enabled. So the path we’re about to go down is one of figuring out how to deal with not enabling the Proceed button until after dynamic content has all loaded up. It’s unknown whether he faces the same challenge/question I have, as in his example we see the dynamic content load up after #2, but don’t have the same various timing tests as I did. Onward! Apparently.

1.14 Creating A Deferred Object

PluralSight: Advanced Techniques in JavaScript and jQuery

We’re going to make our own Deferred object rather than use the one made by jQuery when we use the get method. First, we’ll go back to using the load method since it doesn’t return a deferred object. Here’s the new loadSection, and I’ll continue my commentary as comments within it:

var loadSection = function(options){

            // create a Deferred object
            var defer = $.Deferred(), 

                // create a new variable that we haven't defined yet (it'll be the div we load content into)
                $div;           

                // previously we just created a new options object if options wasn't an object. now, we'll create an error message instead.
            if (typeof options !== 'object')

                // we first return the deferred object (defer), and we .reject() it, which has the effect of "failing" the deferred object.
                // it does so by triggering the fail method, whose first parameter is an object. and that's how we can pass in an object with 
                // the statusText key. down further in our code, recall that we have the .fail() method, which has a single argument (result),
                // which we append to #Messages. It's been an unhelpful failure msg up until this point, but now we're customizing it so it's more
                // useful. 
                return defer.reject({statusText: 'Expecting parameter object'})

            options.selector = options.selector || '';
            options.url = options.url || '';

            // same thing--if the URL is empty, rather than just moving on with the errors, we can force the deferred object to fail and we can give a reason why.
            if (options.url === '')
                return defer.reject({ statusText: 'Missing URL'});

            // kinda the same. we assign the targeted selector to our new variable
            $div = $(options.selector);

            // if length > 0, we know we found the selector on the page
            if ($div.length > 0){
                // in which case we can do `load` instead of `get`
                $div.load(options.url, function(){
                    //and upon successfully loading, do a callback at resolves the deferred object.
                    defer.resolve();
                });
            } else
                // if the length !> 0, then we know the selector wasn't found on the page.
                defer.reject({statusText: 'Error in selector.'})

            // and after we've resolved or rejected the deferred object, we return it.
            return defer;
        }

This loads the content, but two things: the Proceed button is enabled before the dynamic content is loaded. Also, I’m getting the dynamic content loading twice (while in the tutorial it’s only happening once).

Continuing on, I removed all the comments, added a semicolon to the end of one of the lines, and now I’m not getting the dynamic content loading twice. So I undid all my changes. And it’s still working fine. I guess life is just full of mysteries and sometimes you can’t believe your own eyes. Sad face.

var loadSection = function(options){
            var defer = $.Deferred(), 
                $div;

            if (typeof options !== 'object')
                return defer.reject({statusText: 'Expecting parameter object'});

            options.selector = options.selector || '';
            options.url = options.url || '';

            if (options.url === '')
                return defer.reject({ statusText: 'Missing URL'});

            $div = $(options.selector)
            if ($div.length > 0){
                $div.load(options.url, function(){
                    defer.resolve();
                });
            } else
                defer.reject({statusText: 'Error in selector.'})

            return defer;
        }

Let’s do some negative testing. First we run loadSection with no parameters:

var myDefer = $.when(
                loadSection()
                // loadSection({url: 'Content1a.html', selector: '#Section1'}),
                // loadSection({url: 'Content2.html', selector: '#Section2'}),
                // loadSection({url: 'Content3.html', selector: '#Section3'})
            )

Result:

Failure!
Result: Expecting parameter object

Now we’ll add a line with an empty object:

var myDefer = $.when(
                loadSection(),
                loadSection({})
                // loadSection({url: 'Content1a.html', selector: '#Section1'}),
                // loadSection({url: 'Content2.html', selector: '#Section2'}),
                // loadSection({url: 'Content3.html', selector: '#Section3'})
            )

Same result. Why? Aren’t we passing in a parameter object. It’s because it’s displaying the first error message it hits—because it’s returning the deferred object with that specific fail parameter message. So:

var myDefer = $.when(
                // loadSection(),
                loadSection({})
                // loadSection({url: 'Content1a.html', selector: '#Section1'}),
                // loadSection({url: 'Content2.html', selector: '#Section2'}),
                // loadSection({url: 'Content3.html', selector: '#Section3'})
            )

Result:

Failure!
Result: Missing URL

Exactly! Would it be possible to get error messages for each individual loadSection that produces an error? Yes—but we’d have to do that processing within each of those loadSection deferred objects, and not in the object that wraps them all up into one.

1.15 Using Deferred Objects

PluralSight: Advanced Techniques in JavaScript and jQuery

Okay, so I’ll just put in the new code with my notes as comments:

var print = function(msg){
    console.log(msg);
  }

    $(function(){

        // This section lets us do away with repeting bits of code we had. 
        // Lesson learned--when possible, stick things into an object so I can just define them and pass one parameter instead of a ton
        // whose order I have to remember.
        var showMessage = function(options){
            if (typeof options !== 'object')
                options = { message: 'Parameter is not an object', error: true };
            options.message = options.message || 'No message specified';
            options.error = options.error || false;
            $('#Messages')
                .append(options.error ? 'Error: ' : '')
                .append(options.message)
                .append('<br />');
        },

        loadSection = function(options){
            var defer = $.Deferred(), 
                $div, 

                // this is a new variable
                msg;

            if (typeof options !== 'object'){

                // now we're sticking the error msg into a var, and using the new showMessage() to display it
                msg = 'Expecting parameter object';
                showMessage({ message: msg, error: true });

                // and we're still sticking in the deferred object as well
                return defer.reject({statusText: msg});
            }

            options.selector = options.selector || '';
            options.url = options.url || '';

            // this is new
            options.dynamic = options.dynamic || false;

            // expanded similarly to the above, where we add error msg to both the showMessage and the deferred object
            if (options.url === ''){
                msg = 'Missing URL';
                showMessage({ message: msg, error: true});
                return defer.reject({ statusText: msg});
            }

            $div = $(options.selector)
            if ($div.length > 0){

                // returned to .get instead of .load so that we're notified of loading failures -- .load doesn't distinguish bw success/failure, it just lets us
                // know when it's finished processing -- .get returns a deferred object. 
                // we don't need a callback function, so we leave it empty
                $.get(options.url, function(){}, 'html')

                    // on success
                    .done(function(result){

                        // print the content to the div
                        $div.html(result);

                        // and if it doesn't contain dynamic content, resolve it. 
                        if (!options.dynamic){
                            defer.resolve();
                        }
                    })
                    .fail(function(result){
                        msg = 'Could not load URL: ' + options.url;
                        showMessage({ message: msg, error: true });
                        defer.reject(result);
                    });

                // if the content is dynamic
                if (options.dynamic)

                    // remove the handlers for 'complete' and 'failure'
                    // this prevents a bunch of handlers with the same name from ending up on the page in case they
                    // stick around when we're refreshing
                    $div.off('complete, failure')

                    // add the handler for complete
                        .on('complete', function(event){

                            // resolve deferred object
                            defer.resolve();
                        })

                        // add the handler for failure
                        .on('failure', function(event, result){

                            // set the error message on the object, print it, and reject the object.
                            msg = 'Dynamic content: ' + result.statusText;
                            showMessage({ message: msg, error: true });
                            defer.reject(result)
                        });
            } else {
                // if the selector can't target anything
                msg = 'Error in selector';
                ({statusText: msg})
            }

            // we return a promise, not the entire deferred object
            return defer.promise();
        }

        // using shorthand for click
        $('#Load').click(function(){
            // remove the variable
            $.when(

                // showMessage is a function that's a sychronous process / it doesn't return a deferred object. so .when treats it as a resolved deferred object
                // and that's how you can mix sync/async processes
                showMessage({ message: 'Starting processing' }),

                // so if it's dynamic, it doesn't get processed with 'done', but waits for the script to announce that it should be resolved.
                // if we include the dynamic property on a page w/o dynamic content, the $.when wrapper object will never resolve
                loadSection({url: 'Content1b.html', selector: '#Section1', dynamic: true}),
                loadSection({url: 'Content2.html', selector: '#Section2'}),
                loadSection({url: 'Content3b.html', selector: '#Section3', dynamic: true}),
                showMessage({ message: 'Done with processing' })
            )
            .done(function(){
            })
            .fail(function(result){
                $('#Messages').append('Failure!<br/>')
                    .append('Result: ' + result.statusText + '<br />');
            })
            .always(function(){
                $('#Proceed').removeAttr('disabled');
            });
        });
    });

Also, here’s the end of content3b.html:

<br />        $(function(){
            setTimeout(function(){
                $('#Content3')
                    .append('<hr />')
                    .append('Some really important information that you need to read')
                    .trigger('failure',{statusText: 'Content3b triggered error'});
            }, 3000);
        });

And the end of content1b.html

<br />    $(function(){
        setTimeout(function(){
            $('#Content1')
                .append('<hr />')
                .append('more content loaded dynamically')

                // this is the new line. the trigger is on #Content1, while our listener is on #Section1--
                // #Content1 is html on this page, while #Section1 is html on the original (ajax.html) page.
                // this code works bc there's no listener on #Content1, so it bubbles up to #Section1 where it's handled. 
                .trigger('complete');
        }, 5000);
    });

The result? It didn’t work. It loads the content, but the button is never enabled. It worked in the tutorial…let’s figure out why. First, the new stuff that should show up in 3b never appears—which could mean that its deferred object never resolves. Well…it’s targeting #Content3, which doesn’t exist. Let’s switch that to #Section3. Result:

Starting processing
Done with processing
Error: Dynamic content: Content3b triggered error
Failure!
Result: Content3b triggered error

I’m pretty sure that’s how we coded it—so the button is enabled, and we get to see the result of a failure triggered. For whatever reason, it doesn’t go that way in the tutorial. Now let’s see what happens if we give it a bad URL:

Starting processing
Done with processing
Error: Could not load URL: Content1bc.html
Failure!
Result: Not Found
Error: Dynamic content: Content3b triggered error

The issue here is that the button is supposedly enabled before the dynamic content is finished loading. This is up for grabs because our button often seems to enable in stages—it turns from grey to black, but isn’t clickable, and then, once Content2 loads, it becomes clickable. But he says something that makes me think I’d better take notes because he talks fast:

When we reject our deferred object, that immediately rejects the deferred object used by the when block. That causes the failed process and also causes the always method to immediately process. Once the state of a deferred object is established, it can’t be changed.

Let’s go through this. We try to get the URL for Content1. But it’s a bad URL, which triggers the .fail method, which rejects the deferred object for the Content1. The rejected deferred object heads on over to the when clause that loadSection was called from. Now we get the Failure! message on screen. After this, the always function runs, which enables the button.

So what’s the issue again? The issue is that the content that CAN load hasn’t finished loading yet, and yet the button has been enabled. The short reason why: because as soon as a single failure occurs in when, there’s no way for it to succeed, so it continues to the next section of the code, enabling the button—because regardless of whether or not it waits for the rest of the content to load up successful, the when will fail.

The solution is to have the loadSections complete processing, and then after they’ve done so, then determine whether we succeed or fail on the when. His first suggestion: change all the reject methods to resolve methods. If we do this, then regardless of outcome, the deferred objects will resolve—if they produce errors, those errors will be printed to screen.

Starting processing
Done with processing
Error: Could not load URL: Content1bc.html
Error: Dynamic content: Content3b triggered error

Also, the button remains disabled until all three contents are processed, regardless of fail/success. Now, let’s test with no parameter. And also with an empty object as a parameter.

Starting processing
Error: Expecting parameter object
Error: Missing URL
Done with processing
Error: Could not load URL: Content1bc.html
Error: Dynamic content: Content3b triggered error

Next, more negative tests: we’ll target something that doesn’t exist, and we’ll also change Content1bc.html back to Content1b.html and set its dynamic property to false.

Starting processing
Error: Expecting parameter object
Error: Missing URL
Done with processing
Error: Dynamic content: Content3b triggered error

The button doesn’t enable. I should trace this…first I’ll comment out the 1b.html. Oh wow—the button didn’t enable. That means it’s the thing that doesn’t exist? Wow. Yeah, it was. I wouldn’t have guessed that. Actually, I totally guessed it was the other thing. So, how are we currently dealing with a bad targeted element? It’s supposed to say “error in selector” but it’s not. Dig this nonsense:

else {
                msg = 'Error in selector';
                ({statusText: msg})
            }

Alright. Can I solve this on my own? What’s our goal here? The goal is that we see there’s an error, state the error on the screen, and then move on. Let’s see if this works instead:

else {
                msg = 'Error in selector';
                showMessage({ message: msg, error: true});
                defer.resolve()
            }

No difference. Okay. I haven’t thought this through enough. Got it…this works:

{
                msg = 'Error in selector';
                showMessage({ message: msg, error: true});
                defer.resolve({statusText: msg});
            }

Yay we’re all done.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s