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 https://pixelhandler.dev/ 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:
```javascript

(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