Using jQuery's Deferred Functionality For Asynchronous Configuration

Posted June 16, 2011 at 9:52 AM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML

Yesterday, I started looking into a JavaScript pub/sub (publish and subscribe) library that provided asynchronous utility methods. These methods provided UUIDs (universally unique identifiers) and normalized timestamps in order to simplify the development of realtime applications across a global client base. These utility methods made connections to the server and then used a callback-based approach to return the requested value. When I saw that I needed to use multiple asynchronous utility methods in order to fully configure a given entity, I thought it would be the perfect opportunity to use jQuery's Deferred functionality.

It's been a long time since I've looked at Deferred objects in jQuery, so I'll give you the briefest of rundowns. Deferred objects are event "emitting" objects whose encapsulated value will be available at some point in the future (including "now"). When those encapsulated values become available, the deferred object will be considered "resolved" and any callbacks subscribed to the "done" event will be invoked. Conversely, if the encapsulated value fails to load, any callbacks subscribed to the "fail" event will be invoked.

In recent releases of jQuery, the entire AJAX module was rewritten to work with Deferred objects. Additionally, a Deferred constructor method has been provided such that we can wrap any piece of logic with deferred functionality. So, when I saw that this pub/sub API provided multiple, asynchronous utility methods, it seemed like a workflow perfectly suited for deferred management.

In the following demo, I have a user object that I have to configure with a UUID and a timestamp. Both of these methods use a callback-based response mechanism:

  • getUUID( callback )
  • getTime( callback )

In order to make sure that both methods return before I attempt to make use of the user object, I have wrapped them in Deferred functionality and then further packaged them together with a jQuery.when() based deferred workflow.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Using jQuery Deferred For Asynchronous Configuration</title>
  • <script type="text/javascript" src="../jquery-1.6.1.js"></script>
  • <script type="text/javascript">
  •  
  •  
  • // *****************************************************
  • // *****************************************************
  • //
  • // NOTE: If I had control over the API requests, I might
  • // just return a PROMISE from each method, rather than take
  • // a callback. I am using the callback approach to mirror
  • // the 3rd-party API that I was actually dealing with:
  • //
  • // -- getUUID( callback )
  • // -- getTime( callback )
  • //
  • // *****************************************************
  • // *****************************************************
  •  
  •  
  • // I get the UUID from the server. Since this request is
  • // asynchronous, I will pass the result off to the callback.
  • function getUUID( callback ){
  •  
  • // Make the request to the server.
  • var request = $.ajax({
  • type: "get",
  • url: "./get_uuid.cfm",
  • dateType: "text"
  • });
  •  
  • // Handle the success / fail responses.
  • request.then(
  • function( uuid ){
  •  
  • // Invoke the callback with the given UUID.
  • callback( uuid );
  •  
  • },
  • function(){
  •  
  • // Invoke the callback with null.
  • callback( null );
  •  
  • }
  • );
  •  
  • }
  •  
  •  
  • // I get the normalized time from the server. Since this
  • // request is asynchronous, I will pass the result off to
  • // the callback.
  • function getTime( callback ){
  •  
  • // Make the request to the server.
  • var request = $.ajax({
  • type: "get",
  • url: "./get_time.cfm",
  • dateType: "text"
  • });
  •  
  • // Handle the success / fail responses.
  • request.then(
  • function( milliseconds ){
  •  
  • // Invoke the callback with the given date/time.
  • // The milliseconds are coming back as a string;
  • // we are multiplying by 1 to convert to int.
  • callback( new Date( milliseconds * 1 ) );
  •  
  • },
  • function(){
  •  
  • // Invoke the callback with null.
  • callback( null );
  •  
  • }
  • );
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // This is our default user configuration. We will need to
  • // load the UUID and the timestamp from the server in order
  • // to run the rest of the code.
  • var user = {
  • uuid: null,
  • dateCreated: null
  • };
  •  
  •  
  • // Load all the configuration data. Once it is all loaded, we
  • // will be able to run the rest of the scripts.
  • var init = $.when(
  •  
  • // Get the UUID.
  • $.Deferred(
  • function( deferred ){
  •  
  • // Get the uuid; when it comes back, store it in
  • // the user congiruation and resolve the deferred.
  • getUUID(
  • function( uuid ){
  •  
  • // Store the UUID in the user.
  • user.uuid = uuid;
  •  
  • // Reslove the when-based deferred object.
  • deferred.resolve();
  •  
  • }
  • );
  •  
  • }
  • ),
  •  
  • // Get the time.
  • $.Deferred(
  • function( deferred ){
  •  
  • // Get the time; when it comes back, store it in
  • // the user congiruation and resolve the deferred.
  • getTime(
  • function( dateCreated ){
  •  
  • // Store the date/time object in the user.
  • user.dateCreated = dateCreated;
  •  
  • // Reslove the when-based deferred object.
  • deferred.resolve();
  •  
  • }
  • );
  •  
  • }
  • ),
  •  
  • // DOM-ready event.
  • $.Deferred(
  • function( deferred ){
  • $( deferred.resolve );
  • }
  • )
  •  
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the user has finished being initialized, configure
  • // the rest of the scripts (ie. where we would start doing
  • // stuff with the fully loaded user).
  • init.done(
  • function(){
  •  
  • // Log the inidialized user settings.
  • console.log( "Initialized User" );
  • console.log( "UUID:", user.uuid );
  • console.log( "Created:", user.dateCreated );
  •  
  • }
  • );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, I am using the jQuery.when() method in order to group together three asynchronous events: getting the uuid, getting the time, and waiting for the DOM to load. In order to use each of these three asynchronous events within the jQuery.when() method, I have wrapped them in Deferred objects. In this way, the callback used in getUUID() and getTime() can be made to resolve the deferred container.

Once all the deferred objects have been resolved (or failed), the jQuery.when() deferred object invokes its own callbacks; and, when we run the above code, we get the following console output:

Initialized User
UUID: 98AA09E7-BE19-FE0A-C74E8A9DC5E279C9
Created: Date {Thu Jun 16 2011 09:35:36 GMT-0400 (EDT)}

As you can see, at the time of the logging, both asynchronous utility methods have returned successfully.

While it is not entirely relevant to the Deferred exploration, I'll show you the ColdFusion code that powered the two AJAX requests:

get_uuid.cfm

  • <!--- Sleep the thread slightly for demo purposes. --->
  • <cfset sleep( 2 * 1000 ) />
  •  
  • <!--- Create a UUID value. --->
  • <cfset uuid = createUUID() />
  •  
  • <!--- Set the content type and stream it back to the client. --->
  • <cfcontent
  • type="text/plain"
  • variable="#toBinary( toBase64( uuid ) )#"
  • />

As you can see, I am sleeping this thread for 2 seconds in order to test a delayed response in the deferred workflow.

get_time.cfm

  • <!--- Create the normalized time. --->
  • <cfset normalizedTime = getTickCount() />
  •  
  • <!--- Set the content type and stream it back to the client. --->
  • <cfcontent
  • type="text/plain"
  • variable="#toBinary( toBase64( normalizedTime ) )#"
  • />

When I first heard about Deferred objects in jQuery, I wasn't really sure why they'd be useful. Now that I've had a few chances to play with them, however, I absolutely love them. While jQuery's AJAX functionality was always easy to use with a single request, there was never a great way to manage parallel requests. By creating deferred objects, we can now spawn, manage, and respond to all manor of asynchronous functionality with ease.




Reader Comments

Jun 16, 2011 at 10:15 AM // reply »
319 Comments

I'm still trying to wrap my head around deferreds - so the more examples the better.

I think I asked you about this before, but why do you toBinary/toBase64 when you are returning plain text? That shouldn't be necessary afaik.


Jun 16, 2011 at 10:32 AM // reply »
11,238 Comments

@Ray,

As far as the CFContent thing goes, I just like doing it that way because it takes care of resetting the content buffer and controlling the white space.

I could do something like this for the same effect:

  • <cfcontent type="text/plain">#uuid#<cfabort />

This would make sure that there is leading or trailing whitespace around the returned value. But, it's just a preference issue. I like the Variable attribute as it does the same thing.


Jun 16, 2011 at 10:37 AM // reply »
319 Comments

You know there is a reset="true" in cfcontent, right?

<cfcontent type="text/plain" reset="true" variable="#uuid#">

That should work I think.


Jun 16, 2011 at 10:43 AM // reply »
11,238 Comments

@Ray,

Exactly - the Reset attribute actually defaults to yes/true (so it resets the output buffer regardless). The biggest problem is that the Variable attribute can only take a binary value :( If you try to pass it a string, it will throw up the following exception:

Attribute validation error for tag cfcontent. java.lang.String is not a supported variable type. The variable is expected to contain binary data.

I think ColdFusion only ever expected people to use it with file content or PDF documents... but I like to use it with strings! Hence, the toBinary() intermediary step.

Honestly, I wish they would throw a type check inside and do the conversion implicitly. Perhaps a good Feature Request?


Jun 16, 2011 at 10:45 AM // reply »
319 Comments

Well that's just plain stupid. I'm switching to Ruby now!


Jun 16, 2011 at 10:46 AM // reply »
11,238 Comments

@Ray,

Ha ha ha :)


Jun 16, 2011 at 10:47 AM // reply »
4 Comments

Cool! Great real world example of using deferreds with callbacks.


Jun 16, 2011 at 10:49 AM // reply »
319 Comments

Ok, ER filed.

http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=86945


Jun 16, 2011 at 11:10 AM // reply »
11,238 Comments

@Andrew,

Thanks my man :)

@Ray,

Niiice! You have such awesome initiative. I was totally gonna forget about ever filing that.

... and Voted for it.


Jun 16, 2011 at 12:14 PM // reply »
8 Comments

@Ben,

You could do this too:

  • <cfcontent type="text/plain" variable="#uuid.getBytes()#">


Jun 16, 2011 at 12:19 PM // reply »
8 Comments

Or, if the charset is important

  • <cfset uuid = "üäßö"/>
  • <cfcontent type="text/plain" variable="#uuid.getBytes("utf-8")#">


Jun 16, 2011 at 1:14 PM // reply »
11,238 Comments

@Edy,

I go back and forth on the use of the underlying Java methods. I really do like the getBytes() approach; and I do use it sometimes. But then sometimes, I feel guilty about it not being documented.... my brain won't come to rest on the situation :)


Jun 16, 2011 at 4:43 PM // reply »
9 Comments

Well you can add me to the list of people still having trouble grasping the concept of Deferreds, so I'm helpful for any other angles provided.

I do have one question regarding your example, though. Is there a specific reason (e.g. flexibility, performance, etc.) why you decided to go with request.then instead of inline success and error handlers within the AJAX call, or was it mostly for additional Deferred demonstration?


Jun 20, 2011 at 10:25 PM // reply »
11,238 Comments

@JGarrido,

Great question. Now that deferreds allow me to break up the workflow a little bit, I'm finding that it has a couple of benefits:

1 - It cuts down on the indenting of the code (the traditional "boomerang" code that is so common in callback-driven style).

2 - I find that it helps me think a bit more clearly because I can segment my intents. Here's the code that makes the request; and here's the code that handles the response.

3 - It makes it easier for me to add comments. As you can probably see, I LOVE comments :) And, when I can break up lines, it allows for more white-space and commenting, which is something I seem to be addicted to.

All of these, however, are a personal preference. And, when I'm coding apps that have earlier versions of jQuery, I fallback to the inline callback notation without any problem. This approach just feels a few percent more enjoyable.



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 19, 2013 at 2:31 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
It's funny really just how well that image describes the way I would imagine most people that go with angular for some project is. I have had a similar roller-coaster ride with it as well, but not qu ... read »
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools