Rewriting My ‘Guaranteed Callbacks’ Code With jQuery Deferred

In a previous post, I showed some code that I wrote to guarantee the execution of a callback method after a Backbone collection was loaded. Even if you added the callback after the collection had been loaded, it would run. Shortly after writing that blog post, I extracted the code in to my Backbone.Marionette framework as an object called “Callbacks”. It worked and it got the job done, but I knew it could be done better.

Then a few of my friends started talking about jQuery’s “deferred” objects and “promises”. I had heard of this previously but had not bothered to learn it. When I finally decided to dig in to it, this morning, I found that it was a much more robust implementation of what I had just written. I found a good article that helped me understand it better, and began working on an update for Marionette to use deferred objects instead of my own code. Addy Osmani also wrote a really good article on MSDN, which sheds even more light on how these work and how to work with them.

My New “Callbacks” Object

Here’s the end result of my digging in to this and replacing my Callbacks object with a jQuery deferred / promise implementation:

// Callbacks
// ---------

// A simple way of managing a collection of callbacks
// and executing them at a later point in time, using jQuery's
// `Deferred` object.
Marionette.Callbacks = function(){
  this.deferred = $.Deferred();
  this.promise = this.deferred.promise();
};

_.extend(Marionette.Callbacks.prototype, {
  
  // Add a callback to be executed. Callbacks added here are
  // guaranteed to execute, even if they are added after the 
  // `run` method is called.
  add: function(callback){
    this.promise.done(function(context, options){
      callback.call(context, options);
    });
  },

  // Run all registered callbacks with the context specified. 
  // Additional callbacks can be added after this has been run 
  // and they will still be executed.
  run: function(context, options){
    this.deferred.resolve(context, options);
  }
});

This is a reduction in code and complexity, by about 75% on both counts.

Things To Note

Backbone.Marionette.Callbacks is a constructor function and you are expected to instantiate it before using it:

var callbacks = new Backbone.Marionette.Callbacks();

callbacks.add(function(){ … });

I’m using a single deferred object and promise object in this code. That’s done on purpose. I am collection an arbitrary list of callbacks, at an unknown time, and want to guarantee execution of those callbacks. By limiting this code to a single deferred object and a single promise, I can do exactly that.

When you call `add`, it sets up a `done` callback using the single promise object. No matter how many times you call `add`, it uses the same promise for that instance of Callbacks.

Then when you finally call `run`, it resolves the deferred object. Calling `resolve` on the deferred object will kick off all of the callback methods that I had set up with the when/then code.

After you call `run` and resolve the deferred object, you can still add callback functions to this Callbacks instance. When you do add more, jQuery is smart enough to know that the promise has already been fulfilled and it immediately executes the callback that you registered.

Specs And Usage

If you’d like to see the specs for this to show how it works with pre-run registered and post-run registered callback methods, you can grab the source code for Backbone.Marionette and look at the Jasmine specs for Callbacks.

I’m using this Callbacks object in Marionette.Application’s “addInitializer” function, which I’m using to guarantee initializer callback execution. Once I get back to working on the client project that I wrote that original code for, I’ll also update it to use the new Callbacks implementation instead of my original implementation, too.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Async, Backbone, Javascript, JQuery, Marionette. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://twitter.com/jaubourg j@ubourg

    You can make your Callbacks definition even shorter, knowing that Deferred methods are detachable:

    Marionette.Callbacks = function() {    var defer = $.Deferred();    return {        add: defer.done,        run: defer.resolveWith    };};

    • http://mutedsolutions.com Derick Bailey

      nice! … kind of make the whole point of my object go away, too. i think i like that :)

      • http://twitter.com/jaubourg j@ubourg

        Not at all! Keep creating domain-centric objects with clear, semantic, method names: that way, application code is readable and explicit.

        The whole idea behind $.Deferred and $.Callbacks methods being lexically bound is to push people into creating their own utility classes. Just like Addy Osmani demonstrates in his Demistifying $.Callbacks article by creating a pub/sub object ( http://addyosmani.com/blog/jquery-1-7s-callbacks-feature-demystified/ ).

  • http://amccloud.com/ Andrew McCloud

    Awesome! Could you share an example using Marionette.Callbacks vs your Collection.onReset? Would you still need to override Collection.fetch to resolve the callback?

  • http://blogs.fluidinfo.com/terry terrycojones

    Hi Derick. You might find Learning jQuery Deferreds useful (I’m a co-author). If you use AUTHD as a discount code you can get 30%-40% off the list price, at http://shop.oreilly.com/product/0636920030508.do The book has tons of in-depth examples and challenges (with solutions). I love deferreds :-)