← all posts

Using Deferreds in jQuery 1.5

Monday, January 31, 2011

Deferreds, new in jQuery 1.5, decouple logic dependent on the outcome of a task from the task itself. They’re nothing new to the JavaScript scene; Mochikit and Dojo have implemented them for some time, but with Julian Aubourg's AJAX rewrite landing in 1.5, deferreds in jQuery was the logical next step. With deferreds, multiple callbacks can be bound to a task's outcome, and any of these callbacks can be bound even after the task is complete. The task in question may be asynchronous, but not necessarily.

What's more, deferreds are now built-into $.ajax() so you'll get them automatically. Handlers can now be bound like this:

// $.get, an ajax request, is asynchronous by default.
var req = $.get('foo.htm');

req.done(function( response ) {
  // do something with the response
});

req.fail(function() {
  // do something if the request failed
});

// this might execute before the $.get() above is complete
doSomethingAwesome();

// something additional to execute upon success, which might, or might not,
// have fired by now.  With $.ajax deferreds built-in, it doesn't matter.
req.done(function( response ) {
  // do something more with the response
  // this will fire when success normally fires, or fire immediately
  // if prior success callbacks have already fired
});

We are no longer limited to one success, error, or complete handler anymore, and instead of simple callback functions, these hooks are now self-managed first-in, first-out callback queues.

As shown in the example above, callbacks may be attached even after the AJAX request – or any observable task – has completed. For code organization this is great; the days of long unwieldy callbacks may be over. It’s almost as if $.queue() meets pub/sub.

Digging a little deeper here, imagine a scenario where we want to call a function after several concurrent AJAX requests have completed. This is easily accomplished with $.when(), deferred's little helper method:

function doAjax() {
  return $.get('foo.htm');
}

function doMoreAjax() {
  return $.get('bar.htm');
}

$.when( doAjax(), doMoreAjax() )
  .done(function() {
    console.log( 'I fire once BOTH ajax requests have completed!' );
  })
  .fail(function() {
    console.log( 'I fire if one or more requests failed.' );
  });

View this example on jsFiddle

The reason this works is because all of jQuery's AJAX methods now return an object containing a "promise", which is used to track the asynchronous request. The promise is a read-only view into the result of the task. Deferreds look for the presence of a promise() method to determine whether an object is observable or not. The $.when() waits for all its AJAX requests to execute, and once they do, the callbacks attached to the $.when() via .done() and .fail() will fire as appropriate (depending on task's success or failure state). The callbacks fire in the order they were assigned.

It gets better: all deferred's methods accept either functions or arrays of functions, so you can build your behaviors and assign them all with one call, or in separate calls, as you please.

$.ajax() returns an object packed with other deferred-related methods. I discussed promise(), but you’ll also find then(), done(), fail(), and a host of others. You don’t have access to the complete deferred object, though; only the promise, callback-binding methods, and the isRejected() and isResolved() methods, which can be used to check the state of the deferred.

But why not return the whole object? If this were the case, it would be possible to muck with the works, maybe pragmatically "resolve" the deferred, causing all bound callbacks to fire before the AJAX request had a chance to complete. Therefore, to avoid potentially breaking the whole paradigm, only return the dfd.promise().

Registering Callbacks

In the examples thus far I've used done(), and fail() methods to register callbacks onto the deferred, but there are more methods available to you, especially when working with AJAX deferreds. The method you choose ultimately depends on the resolution state(s) you’d like to bind to.

Available to all deferreds (AJAX, $.when, and those created manually):

.then( doneCallbacks, failedCallbacks )
.done( doneCallbacks )
.fail( failCallbacks )
As of jQuery 1.8, jqXHR.success(), jqXHR.error(), and jqXHR.complete() are deprecated. Use done(), fail(), and always() instead.

AJAX deferreds have three additional methods, two of which map to one of the above. They are provided as semantic alternatives and match the names of the "old" handlers we're all used to:

// "success" and "error" map to "done" and "fail" respectively.
.success( doneCallbacks )
.error( failCallbacks )

You can register a complete handler that'll fire regardless of the success or failure state of the request. Unlike success and error, complete is actually an alias to the done method of a separate deferred. This separate deferred, created internally by $.ajax(), is resolved after an AJAX request completes, regardless of the outcome.

.complete( completeCallbacks );

Therefore, the following three examples are equivalent (success reads better than done in the context of an AJAX request, don't you think?)

$.get("/foo/").done( fn );

// same as:

$.get("/foo/").success( fn );

// same as:

$.get("/foo/", fn );

Creating your own Deferred

We know that $.ajax and $.when implement the deferred API internally, but you can also create your own implementations:

function getData() {
  return $.get('/foo/');
}

function showDiv() {
  var dfd = $.Deferred();

  $('#foo').fadeIn( 1000, dfd.resolve );

  return dfd.promise();
}

$.when( getData(), showDiv() ).done(function( ajaxResult ) {
  console.log('The animation AND the AJAX request are both done!');

  // 'ajaxResult' is the server's response
});

View this example on jsFiddle

Inside showDiv() I'm creating a new deferred object, performing an animation, and returning the promise. The deferred is resolved (think of dequeue() if you’re familiar with jQuery's queuing methods) after the fadeIn() call completes. Between the time the promise is returned and the deferred is resolved, a done() callback is registered to the successful completion of both asynchronous tasks. Therefore, once both tasks resolve, the callback is fired.

getData() returns an object with a promise method, which allows $.when() to observe its eventual resolution. The manually steps we took to return a promise in showDiv() is handled for us internally by $.ajax() and $.when().

1/15/2011: As Julian pointed out in the comments, the above syntax can be shortened using the $.Deferred(fn).promise() signature. The following two approaches to creating a deferred are equivalent:
function showDiv() {
  var dfd = $.Deferred();

  $('#foo').fadeIn( 1000, dfd.resolve );

  return dfd.promise();
}

// same as:

function showDiv() {
  return $.Deferred(function( dfd ) {
    $('#foo').fadeIn( 1000, dfd.resolve );
  }).promise();
}

Defer your Deferreds

We could take this one step further by registering individual callbacks to both getData() and showDiv(), as well as registering their individual promises onto one "master" deferred.

If you wanted something to happen on the success of getData() and on the success of showDiv() (independently of the other), as well as on the success of both getData() and showDiv() combined, simply register a callback to their individual deferreds, and tie them together with $.when:

function getData() {
  return $.get('/foo/').success(function() {
    console.log('Fires after the AJAX request succeeds');
  });
}

function showDiv() {
  return $.Deferred(function( dfd ) {
    $('#foo').fadeIn( 1000, dfd.resolve );
  }).promise();
}

$.when( getData(), showDiv() ).done(function( ajaxResult ) {
  console.log('Fires after BOTH showDiv() AND the AJAX request succeed!');

  // 'ajaxResult' is the server’s response
});

View this example on jsFiddle

Chaining Hotness

Deferred callbacks can be chained so as long as a promise is returned from the function. Here's a real world example (via @ajpiano!)

function saveContact( row ) {
  var form = $.tmpl(templates["contact-form"]),
      valid = true,
      messages = [],
      dfd = $.Deferred();

  /*
     bunch of client-side validation here
   */

  if( !valid ) {
    dfd.resolve({
      success: false,
      errors: messages
      });
  } else {
    form.ajaxSubmit({
      dataType: "json",
      success: dfd.resolve,
      error: dfd.reject
    });
  }

  return dfd.promise();
};

saveContact( row ).done(function(response) {
  if( response.success ) {
    // saving worked; rejoice
  } else {
    // client-side validation failed
    // output the contents of response.errors
  }
}).fail(function(err) {
  // AJAX request failed
});

The saveContact() function first validates the form and saves the result into the variable valid. If validation fails, the deferred is resolved with an object containing a success boolean and an array of error messages. If the form passes validation, the deferred is resolved, except this time the success handler receives the response from the AJAX request. The fail() handler responds to 404, 500, and other HTTP errors that could prevent the AJAX request from succeeding.

Non-observable Tasks

Deferreds are particularly useful when the logic to execute may or may not be asynchronous, and you want to abstract that condition out of the mainline code. Your task might return a promise, but it might also return a string, object, or some other type.

In this example, the first time a "launch application" link is clicked on, an AJAX request tells the server to record (and return) the current timestamp. The timestamp is stored in the element's data cache after the AJAX request is complete. The application only cares about the initial first click though, so on subsequent clicks, the timestamp is read out of the data cache instead of making an additional trip to the server.

function startTask( element ) {
  var timestamp = $.data( element, 'timestamp' );

  if( timestamp ) {
    return timestamp;
  } else {
    return $.get('/start-task/').success(function( timestamp ) {
      $.data( element, 'timestamp', timestamp );
    });
  }
}

$('#launchApplication').on('click', function( event ) {
  event.preventDefault();

  $.when( startTask(this) ).done(function( timestamp ) {
  $('#status').html( 'You first started this task on: ' + timestamp);
  });

  loadApplication();
});

When $.when() recognizes that its first argument doesn't have a promise (and therefore is not observable), it creates a new deferred object, resolves it with the data, and returns the promise from the deferred. As such, something arbitrary without an initial promise can be observed.

One small gotcha to be aware of (which will most likely be addressed in the next maintenance release), is that you cannot defer an object that implements it’s own promise method. Deferreds are detected by the presence of a promise method, but jQuery doesn't check to see if the promise actually returns a usable object. Therefore, this will throw a syntax error:

var obj = {
  promise: function() {
    // do something
  }
};

$.when( obj ).done( fn );

Conclusion

Deferreds introduce a new, robust approach to writing asynchronous tasks. Instead of focusing on how to organize callback logic into a singular callback, you can assign several individual actions to callback queues knowing that these will be executed, in context, without worrying so much about synchronicity. The info here might be a lot to digest, but once you get the hang of it, I think you'll find writing asynchronous code to be much easier with deferreds.

Many thanks to Steven Black for editing this post, as well as the #jquery-dev crew for reinforcing my understanding of deferreds. Adam hooked me up with a sweet example, too.

comments powered by Disqus