Use Newer jQuery Features When Your App Is Stuck on Older Version

Billy Heaton 7 min read

Links on the topic of Deferreds / Promises

Short explanation

  • You really want to use jQuery methods : $.Deferred() .promise() .done() .fail() .isResolved() .isRejected() .then() .always() .pipe() .when()
  • You may be using a service to get some data e.g. via .ajax()
  • You need both ajax actions to complete (with success) then your code responds after both actions are done.
  • See Gists below ... https://gist.github.com/1273143 | https://gist.github.com/1273133

See working example at : http://pixelhandler.com/downloads/code/deferred-promise/

Code …

<!docType html>
<html>
<head>
  <title>Deferred / Promise</title>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript" src="jquery-1.6.4-promises.js">// get script at https://gist.github.com/1273133</script>
</head>
<body>
<h1>Deferred / Promise Example using jQuery 1.4.2 by adding implementation in newer version</h1>
<script type="text/javascript" charset="utf-8">

(function($){ // IIFE ...

$(function(){ // doc ready ...

  var container, things, promises;

  // Code below borrowed from ...
  //    Deferreds post : http://quickleft.com/blog/jquery-15-hotness-part-2
  //    Code : https://gist.github.com/862567

  container = $( "<div/>" ).appendTo( document.body );
  $('div').html("<p>jQuery version:" + jQuery.fn.jquery + ". Hang on this should take about 5 seconds to complete.</p>");

  // List of resources we want to load files on http://pixelhandler.com/ domain
  things = [
    {
      url: "/downloads/code/deferred-promise/services/?type=XML",
      data_type: "xml",
      name: "Products XML"
    },
    {
      url: "/downloads/code/deferred-promise/services/?type=JSON",
      data_type: "json",
      name: "Products JSON"
    }
  ];

  // Container to hold our promises
  promises = [];

  function getSomething(thing){

    // Create our deferred
    var dfd = $.Deferred();

    // - Make a request and resolve our deferred onSuccess
    // - You can get super fancy here with $.ajax and explicitly
    //   set fail or rejected states
    $.get(
      thing.url,
      function() { dfd.resolve(); },
      thing.data_type
    );

    // We return a promise so we can watch it for a resolution
    return dfd.promise();

  }

  // Iterate through our requests
  $.each( things, function(_, thing){
    // - Each getSomething() call will return a promise, so let's
    //   push them into our promises array
    promises.push( getSomething(thing).done(function(){
      // - This is an inline done() handler which will fire when
      //   the individual promise is resolved
      container.append( '<p>Loaded ' + thing.name  + '. - Promise done.</p>' );
      // - You could string other handlers like fail() after this
      //   if you want to try a broken link or other situation
    }) );
  });

  setTimeout(function(){

    // Apply entire array of promises to a $.when listener
    $.when.apply( null,  promises ).then(function(){
      // All promises have been resolved :)
      container.append( '<p class="green">Finished loading All Files - All promises have been resolved. (We can even wait a bit before we handle our promises. If they have already been resolved, jQuery will remember!)</p>' );
    });

  }, 5000);

}); // end ready

}(jQuery)); // end IIFE

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

Uses code from:

(function($){

    var // Promise methods
        promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
        // Static reference to slice
        sliceDeferred = [].slice;

    if (typeof $ !== 'function') {
        return false;
    } else {
        jQuery.each(promiseMethods, function(){
            if (typeof jQuery[this] !== 'undefined') {
                return false;
            }
        });
    }

    // dependencies
    if (typeof jQuery.type === 'undefined') {
        var class2type = {};
        // Populate the class2type map
        jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
            class2type[ "[object " + name + "]" ] = name.toLowerCase();
        });
        jQuery.extend({
            type: function( obj ) {
                return obj == null ?
                    String( obj ) :
                    class2type[ toString.call(obj) ] || "object";
            }
            // redefine isFunction using type method
            //isFunction: function( obj ) {
            //  return jQuery.type(obj) === "function";
            //}
        });

    }

    jQuery.extend({
        // Create a simple deferred (one callbacks list)
        _Deferred: function() {
            var // callbacks list
                callbacks = [],
                // stored [ context , args ]
                fired,
                // to avoid firing when already doing so
                firing,
                // flag to know if the deferred has been cancelled
                cancelled,
                // the deferred itself
                deferred  = {

                    // done( f1, f2, ...)
                    done: function() {
                        if ( !cancelled ) {
                            var args = arguments,
                                i,
                                length,
                                elem,
                                type,
                                _fired;
                            if ( fired ) {
                                _fired = fired;
                                fired = 0;
                            }
                            for ( i = 0, length = args.length; i < length; i++ ) {
                                elem = args[ i ];
                                type = jQuery.type( elem );
                                if ( type === "array" ) {
                                    deferred.done.apply( deferred, elem );
                                } else if ( type === "function" ) {
                                    callbacks.push( elem );
                                }
                            }
                            if ( _fired ) {
                                deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
                            }
                        }
                        return this;
                    },

                    // resolve with given context and args
                    resolveWith: function( context, args ) {
                        if ( !cancelled && !fired && !firing ) {
                            // make sure args are available (#8421)
                            args = args || [];
                            firing = 1;
                            try {
                                while( callbacks[ 0 ] ) {
                                    callbacks.shift().apply( context, args );
                                }
                            }
                            finally {
                                fired = [ context, args ];
                                firing = 0;
                            }
                        }
                        return this;
                    },

                    // resolve with this as context and given arguments
                    resolve: function() {
                        deferred.resolveWith( this, arguments );
                        return this;
                    },

                    // Has this deferred been resolved?
                    isResolved: function() {
                        return !!( firing || fired );
                    },

                    // Cancel
                    cancel: function() {
                        cancelled = 1;
                        callbacks = [];
                        return this;
                    }
                };

            return deferred;
        },

        // Full fledged deferred (two callbacks list)
        Deferred: function( func ) {
            var deferred = jQuery._Deferred(),
                failDeferred = jQuery._Deferred(),
                promise;
            // Add errorDeferred methods, then and promise
            jQuery.extend( deferred, {
                then: function( doneCallbacks, failCallbacks ) {
                    deferred.done( doneCallbacks ).fail( failCallbacks );
                    return this;
                },
                always: function() {
                    return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
                },
                fail: failDeferred.done,
                rejectWith: failDeferred.resolveWith,
                reject: failDeferred.resolve,
                isRejected: failDeferred.isResolved,
                pipe: function( fnDone, fnFail ) {
                    return jQuery.Deferred(function( newDefer ) {
                        jQuery.each( {
                            done: [ fnDone, "resolve" ],
                            fail: [ fnFail, "reject" ]
                        }, function( handler, data ) {
                            var fn = data[ 0 ],
                                action = data[ 1 ],
                                returned;
                            if ( jQuery.isFunction( fn ) ) {
                                deferred[ handler ](function() {
                                    returned = fn.apply( this, arguments );
                                    if ( returned && jQuery.isFunction( returned.promise ) ) {
                                        returned.promise().then( newDefer.resolve, newDefer.reject );
                                    } else {
                                        newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
                                    }
                                });
                            } else {
                                deferred[ handler ]( newDefer[ action ] );
                            }
                        });
                    }).promise();
                },
                // Get a promise for this deferred
                // If obj is provided, the promise aspect is added to the object
                promise: function( obj ) {
                    if ( obj == null ) {
                        if ( promise ) {
                            return promise;
                        }
                        promise = obj = {};
                    }
                    var i = promiseMethods.length;
                    while( i-- ) {
                        obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
                    }
                    return obj;
                }
            });
            // Make sure only one callback list will be used
            deferred.done( failDeferred.cancel ).fail( deferred.cancel );
            // Unexpose cancel
            delete deferred.cancel;
            // Call given func if any
            if ( func ) {
                func.call( deferred, deferred );
            }
            return deferred;
        },

        // Deferred helper
        when: function( firstParam ) {
            var args = arguments,
                i = 0,
                length = args.length,
                count = length,
                deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
                    firstParam :
                    jQuery.Deferred();
            function resolveFunc( i ) {
                return function( value ) {
                    args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
                    if ( !( --count ) ) {
                        // Strange bug in FF4:
                        // Values changed onto the arguments object sometimes end up as undefined values
                        // outside the $.when method. Cloning the object into a fresh array solves the issue
                        deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
                    }
                };
            }
            if ( length > 1 ) {
                for( ; i < length; i++ ) {
                    if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
                        args[ i ].promise().then( resolveFunc(i), deferred.reject );
                    } else {
                        --count;
                    }
                }
                if ( !count ) {
                    deferred.resolveWith( deferred, args );
                }
            } else if ( deferred !== firstParam ) {
                deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
            }
            return deferred.promise();
        }
    });


}(jQuery));

About the Author

Billy Heaton
Billy Heaton

Software engineer with two decades of experience who favors Ruby and JavaScript to build web applications