What happens when your JavaScript code depends on not only multiple asynchronous scripts, but also one or more JSONP calls? Once again, jQuery.Deferred() and jQuery.when() save the day.
In Part I of this article: “Complex Multiple Script Loading Using the jQuery.when() Method and the jQuery.Deferred Object Part I“, we discussed the complex nature of multiple asynchronous dependencies. In that article, we were able to count on jQuery.when() to pay attention to the resolution of our jQuery.Deferred objects. In this second part, we’ll complicate things even further by adding a JSONP dependency. The “complication” is the fact that the success of the JSONP call requires us to re-think how we will leverage our jQuery.Deferred object’s resolution.
In my opinion, the asynchronous script calls are fairly easy to manage:
1 2 3 |
var foo = jQuery.getScript(SCRIPT_URL).done(function(){ //confidently act upon the script }); |
But what about a JSONP call? How do we squeeze that scenario into the jQuery.Deferred / jQuery.when() paradigm?
The approach I have taken starts with a variable that is set to an unresolved jQuery.Deferred object. As soon as you create this variable, you can then program around it. In the true spirit of JSONP, you can name your callback whatever you want. If the server supports JSONP, then you can assume that it will call your callback, passing in the much-desired return data.
Here is a pseudo-code walk-through:
Example # 1
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 |
// Step # 1) create the jQuery.Deferred object var foo = jQuery.Deferred(); // Step # 2) setup the function that will handle the resolved state of our jQuery.Deferred object jQuery.when(foo).done(function(){ //do the stuff that depends on foo }); // Step # 3) create the JSONP callback var myCallback = function(data){ //cache the date window.myData = data; //resolve the deferred that represents this async call foo.resolve(); }; // Step # 4) make the JSONP call var script = document.createElement(‘script); script.src = ‘http://mydomain.com/myJson.js&calback=myCallback’; document.getElementsByTagName(head’)[0].append(script); |
In Example # 1, we have the core code that makes this all work. The interesting thing is that the actual JSONP call comes last. What we do beforehand is set up the tools we need to handle this asynchronous event. What makes this scenario a bit special is that you manually resolve the jQuery.Deferred object in the JSONP callback: “foo.resolve()”. This very nicely kicks in the .when() method that is chained to the return value of the jQuery.when() call.
Example # 2
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Complex Multiple Script Loading Using the jQuery.when() Method and the jQuery.Deferred Object Part II - Example Code</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> <style> /* css removed for brevity */ </style> </head> <body> <div class="generic"> <h1>jQuery.Deferred() and .when() Example</h1> <h2>Complex Multiple Script Loading Using the jQuery.when() Method and the jQuery.Deferred Object - Part II || <a href="http://blog.kevinchisholm.com" target="_blank">blog.kevinchisholm.com</a></h2> </div> <div id="container"></div> <script> var //urs for script calls mustacheJsUrl = 'https://raw.github.com/janl/mustache.js/master/mustache.js', jqformValUrl = 'https://raw.github.com/sdellow/jQuery-Form-Validation/master/jquery.form-validation.js', //dummy data for mustache.js rendering of form data = { fieldName: "first_name", label: "First Name", submit: "Submit this form", error: "Please be sure to enter your first name" }, //the html template for the form, used by mustache.js template = '' + '<form id="testform">' + '<div class="field">' + '<label for="{{fieldName}}">{{label}}</label>' + '<input type="text" id="{{fieldName}}" name="{{fieldName}}" class="required">' + '<button type="submit">{{submit}}</button>' + '<div class="val-message">{{error}}</div>' + '</div>' + '</form>', //the html template for the jsonp demo, used by mustache.js itemTemplate = '' + '<li>' + '<b>name: </b>{{name}}<br />' + '<b>title: {{title}}<br />' + '<b>phone number: {{phone}}<br />' + '<b>account number: {{accountNumber}}<br />' + '</li>', //fonfirm that the jquery.form-validation.js plugin is loaded, or load it confirmFormVal = function (){ //is jquery.form-validation.js loaded on this page? if(!jQuery.fn.formval){ //if not, load it return $.when($.getScript(jqformValUrl)); } else { //it's loaded, just return the resolved jQuery deferred object console.log('the formval jQuery plugin is already loaded...'); return jQuery.Deferred().resolve();; }; }, //fonfirm that mustache.js is loaded, or load it confirmMustacheJs = function (){ //is mustache.js loaded on this page? if(!window.Mustache || !window.Mustache || !window.Mustache.name === 'mustache.js'){ //if not, load it return $.when($.getScript(mustacheJsUrl)); } else { //it's loaded, just return the resolved jQuery deferred object console.log('mustache.js is already loaded...'); return jQuery.Deferred().resolve(); }; }, //used to cache the jsonp data jsonpData = [], //the jsonp callback myCallback = function(data){ //cache the data jsonpData = data; //resolve the deferred object that represents this jsonp call jsonpIsLoaded.resolve(); }, //jQuery.deferred objects to represent the asycronous calls formValIsLoaded = confirmFormVal(), mustacheIsLoaded = confirmMustacheJs(), jsonpIsLoaded = jQuery.Deferred(); //act upon the resolution of the jQuery.sdeferred objects $.when(formValIsLoaded,mustacheIsLoaded,jsonpIsLoaded).done(function(){ var i = 0, //cached jsonp data arr = jsonpData, arrLen = jsonpData.length, //render the form using mustache.js form = Mustache.render(template,data), //markup used to demonstrate use of returned jsonp data $ul = $('<ul id="jsonpTarget"></ul>'), $ulContainer = $('<div class="generic"><h3>Sales Team</h3></div>').append($ul); //append the ul to the dom $('body').append($ulContainer); //inject the form $('#container').append(form); //setup the form for validation (form validation plugin) $('#testform').formval(); //create li items based on the returned jsonp data for(; i < arrLen; i++){ $('#jsonpTarget').append(Mustache.render(itemTemplate,arr[i])); }; }); //make the jsonp call $.getScript('http://examples.kevinchisholm.com/utils/json/jsonp.php?callback=myCallback'); </script> </body> </html> |
In Example # 2, we have the code for the full working example. It builds upon the full working example from Part I of this article, but the added functionality has no connection to the form validation. The point that is (hopefully) being made here is:
- This code provides a way to ensure that multiple asynchronously loaded dependencies are loaded before further action is taken.
- This code checks to see if any of the asynchronously loaded dependencies have already been loaded in the page (possibly by hard-coded synchronous calls or another script).
- This code allows for the inclusion of an asynchronously loaded JSONP dependency.
Going through the code line-by-line would be repetitive as that was already done in Part I. What might be helpful is to look at the code and see where the approach taken in Example # 1 has been worked into the full working code (Example # 2).
Here is the URL for the full working example: http://examples.kevinchisholm.com/jquery/deferred-and-when/complex-multi-async-dependencies.html
Summary
In this article we learned how to accommodate a JSONP call as an asynchronous dependency. We leveraged the jQuery.Deferred object and the jQuery.when() method to manage and act upon the resolution of the JSONP call.