Deferred objects appeared in jQuery 1.5. They allow you to separate the logic that depends on the results of the action from the action itself. For JavaScript Deferred objects are not new, they were already in MochiKit and Dojo, but with changes to jQuery ajax logic from Julian Aubourg, the introduction of Deferred objects was inevitable. With Deferred objects, several callbacks can be associated with the result of the task and any of them can be tied to the action even after it has started. The task to be performed may be asynchronous, but not necessary.
Deferred objects are now built into $ .ajax () so you will receive them automatically. Handlers can now be associated with the result as follows:
We are no longer limited to one successor, error or complete handler, we now have self-organizing callback queues (FIFO), instead of simple callback functions.
As shown in the example above, callbacks can be hooked even after an AJAX request or after completing any task. This is great for organizing code, the days of long callback blocks are numbered.
')
Digging deeper, imagine a situation in which we want to call a function after several simultaneous AJAX requests. This is easy to do with the helper of pending objects - $ .when ():
function doAjax(){ return $.get('foo.htm'); } function doMoreAjax(){ return $.get('bar.htm'); } $.when( doAjax(), doMoreAjax() ) .then(function(){ console.log( 'I fire once BOTH ajax requests have completed!' ); }) .fail(function(){ console.log( 'I fire if one or more requests failed.' ); });
JsFiddle
example jsfiddle.net/ehynds/Mrqf8This all works because now all jQuery AJAX methods return an object containing a Promise object that is used to track asynchronous calls. Promise is an observable result watcher. Deferred objects look for the presence of the promise () method to determine whether it is possible to monitor the object or not (object is observable or not). $ .when () waits for all AJAX requests to be executed, when this happens the callback functions attached to $ .when () via .then () and .fail () will be called depending on what the result is. Callback functions are called in the order of arrival (fifo queue).
All deferred methods (.then () and .fail ()) accept functions or arrays of functions, so you can create your own behaviors and assign them all with a single call or separate them at your convenience.
$ .ajax () returns a non-standard object containing another Deferred-like object. I have already described promise (), but you also found then (), success (), error () and many others. You do not have access to the entire Ajax-Deferred object. Only promise (), callback methods, as well as isRejected () and isResolved () methods, which can be used to check the status of a Deferred object.
But why not return the entire object? If this were the case, then it would be possible to programmatically complete the Ajax-Deferred object, without waiting for the Ajax response. This potentially disrupts the entire paradigm.
Register Callbacks
In the examples shown above, the then (), success () and fail () methods are used to register callback functions in Deferred objects, but more methods are available to you, especially when you work with Ajax-Deferred.
Methods are available to all Deferred objects (AJAX, $ .when, and those created by hands):
.then( doneCallbacks, failedCallbacks ) .done( doneCallbacks ) .fail( failCallbacks )
The Ajax-Deferred object has 3 additional methods that simply duplicate the ones described above. They provide semantic alternatives and are similar in name to the old handlers to which we are all used:
.complete( doneCallbacks, failCallbacks ) .success( doneCallbacks ) .error( failCallbacks )
Thus, the following three examples are equivalent (success reads better than if it were in the context of an AJAX request, isn’t it?)
$.get("/foo/").done( fn );
Creating Your Deferred Behavior
We know that the $ .ajax and $ .when methods provide us with a deferred API inside, but we can make our own variant:
function getData(){ return $.get('/foo/'); } function showDiv(){ var dfd = $.Deferred(); $('#foo').fadeIn( 1000, dfd.resolve ); return dfd.promise(); } $.when( getData(), showDiv() ) .then(function( ajaxResult ){ console.log('The animation AND the AJAX request are both done!');
JsFiddle
example jsfiddle.net/ehynds/JSw5yInside showDiv (), I create a new Deferred object that performs the animation and returns the promise. Deferred ends (remember dequeue () if you are familiar with the methods of working with queues in jQuery) after fadeIn () is completed. Between the moment the promise returns and Deferred ends, we register a callback waiting for the successful execution of both tasks. Therefore, when both tasks are completed, the callback will be executed.
getData () returns an object with a promise method that allows $ .when () to monitor the execution of an action. Similar object returns showDiv ()
Deferred Objects Pending
We can take one more step to register individual callback functions for getData () and showDiv () and register their promises in one master-Deferred object.
If you want something to happen after executing success getData () or success showDiv () independently, as well as after the success of both getData () and showDiv (), simply register a callback for their individual Deferred objects and link them together via $ .when
function getData(){ return $.get('/foo/').success(function(){ console.log('Fires after the AJAX request succeeds'); }); } function showDiv(){ var dfd = $.Deferred(); dfd.done(function(){ console.log('Fires after the animation succeeds'); }); $('#foo').fadeIn( 1000, dfd.resolve ); return dfd.promise(); } $.when( getData(), showDiv() ) .then(function( ajaxResult ){ console.log('Fires after BOTH showDiv() AND the AJAX request succeed!');
JsFiddle
example jsfiddle.net/ehynds/W3cQcDeferred Chains
Deferred callbacks can be represented as chains, since promise returns from callback methods. Here is a real example from
@ajpiano :
function saveContact( row ){ var form = $.tmpl(templates["contact-form"]), valid = true, messages = [], dfd = $.Deferred(); if( !valid ){ dfd.resolve({ success: false, errors: messages }); } else { form.ajaxSubmit({ dataType: "json", success: dfd.resolve, error: dfd.reject }); } return dfd.promise(); }; saveContact( row ) .then(function(response){ if( response.success ){
The saveContact () function checks the form and saves the result in the valid variable. If validation fails, the deferred object is completed with an object containing the succession and an array of errors. If the form is validated, the deferred object is completed, but this time with a response from the AJAX request. The fail () handler listens for transport errors (404, 500, and others)
Not observable tasks
Deferreds are especially useful when the execution logic may or may not be asynchronous, and you want to abstract this condition from the underlying code. Your task may return a promise, but it may also return a string, an object, or something else.
In this example, when we first click on the launch application link, an AJAX request tells the server to save and return the current timestamp. Timestamp is stored in the item's data cache after an AJAX request has been completed. The app only cares about the first click. On subsequent clicks, the timestamp is taken from the data cache, instead of making a request 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').bind('click', function( event ){ event.preventDefault(); $.when( startTask(this) ).done(function( timestamp ){ $('#status').html( '<p>You first started this task on: ' + timestamp + '</p>'); }); loadApplication(); });
When $ .when () realizes that its first argument has no promise (and therefore it is not observable), it creates a new deferred object and terminates it with the data object and returns the promise of this pending object. So that any object without promise can be observed.
One small thing, you can't do a deferred launch of an object that returns an object with its promise method. Deferred objects are determined about the presence of the promise method, but jQuery does not check whether the promise returns a necessary object. Therefore, this code contains an error syntax error:
var obj = { promise: function(){
Conclusion
Deferred objects introduce a new reliable way to write asynchronous tasks. Instead of focusing on how to organize the callback logic in one callback, you can assign several separate actions to the callback queue, knowing that they will be executed, without worrying so much about their synchronicity. The proposed information needs to be digested for a long time, but when you learn everything, then I think you will understand that asynchronous code is much simpler with Deferred objects.