Monday, February 13, 2012

jQuery : Exploring Deferred and Promise

The jQuery Deferred object was introduced as a part of the 1.5 release of the framework. The deferred object in jQuery is based upon the the concept of Promises. In order to understand all about deferred objects, it would wise to try to understand what a Promise is all about.

Many times in our daily lives, we cannot rely on the outcomes of certain actions. However, we may still need to make decisions about the future depending upon an anticipated outcome. All we know for sure is that we are gonna get an outcome. Lets just say that we wrap the term 'outcome' in fancy looking gift wrapper and call it a 'Promise'.

So, what is a 'Promise'? Well, its nothing more than an object returned by a method based upon which you can determine a future course of action.

Lets take a real world analogy.

You decide to take up a job interview for company X - (This is a function that you intend to perform)
The interview has an outcome. You either get the job or you dont.
But you need to have a plan no matter what the outcome of the interview is. And you need to plan that now. You cannot plan it in the future after the interview. For example, you may need to book a travel ticket, ,or may have to book a place for a party if the interview goes through. If it does not go through, then you may have to buy up some books and apply to company Y.

If we were to write this plan programmatically, we would end up writing something like this.

candidate.attendInterview().then(
successFunction(){
 //Book tickets.
 //Order a crate of beer for a party!
 },
failureFunction(){
 //Buy some more books to brush up
 //Apply to company Y
}
);


Now what is interesting about this is that even though you dont know the exact outcome of the attendInterview function, you are able to chain a method to the 'supposed' outcome and specify the functions that should be invoked upon completion of the 'attendInterview' function. This is possible because the attendInterview function would return a 'Promise' object. An interesting aspect of this pattern is the concept of promise resolution. Promise resolution is said to happen when the task that returns the promise completes in reality. So, when you return a Promise object from the attendInterview function, the Promise is said to be unresolved. Only when the result of the operation is available, i.e. when the actual outcome of the interview is obtained after a period of time, the promise object is said to be resolved. Upon resolution, based upon the result of the resolution, either the successFunction or the failureFunction will be invoked.

The concept of promises is important because of three main reasons 

  1. It helps you to decouple the logic of future handlers from the actual task invocation. 
  2. You can add any number of future handlers. 
  3. If you add a handler to an already resolved Promise, the appropriate(success of failure) function would fire immediately.

The advantages of using a promise become much more clear when used in the context of ajax requests. That's because the result of an ajax request is available at a future point of time, and the amount of time required for the completion of an ajax request cannot be determined beforehand.

jQuery introduced the Deferred object in the 1.5 version of the library to handle this scenario. The Deferred object is actually an implementation of the Promise interface. It provides all the features of a Promise, and in addition, it also allows you to create new deferred objects and attach future handlers to them and resolve them programmatically.

Lets understand the jQuery deferred object in terms of an ajax request.

In versions of jQuery prior to 1.5 , in order to attach a success or a failure handler to an ajax request, one would have declare a success callback function when defining the ajax call. Although much more convenient than the under the covers xmlhttprequest handling, this scheme had one drawback. A function that would be invoked in the future (success or failure) had to be defined as a part of the ajax function. Moreover, one could not attach multiple success or failure handlers to the ajax call.

Here is how one would request using a version of jQuery prior to 1.5

$.ajax({
  url: "test.html",
  success: function(){
    alert("ajax request succesful");
  },
  failure: function(){
    alert("ajax request failed");
  }
});


As shown in the code above, it can be see that you are bound to specify the success function while making the ajax request. Moreover there is only one available callback for success and failure each.

As of jQuery 1.5, the introduction of the Deferred changed the game drastically. And its a progressive change to be honest, because it not only adds more effectiveness to the ajax functionality, but it also adds more functionality to the framework as a whole. We shall see how.

Let us now invoke the same ajax call that we saw earlier using a version of jQuery > 1.5.

var myRequest = $.ajax({  url: "test.html" })
 .done(function(data){ 
  alert('ajax request was successful');
  })
 .fail(function(data){ 
  alert('ajax request failed');
 });
 
 //After a few more lines of code
 myRequest.done(function(data){
  $('div.myclass').html('Ajax Response ' + data);
 });

As you see, this is far better than the previous method of invocation of the ajax request. The done and fail functions are used to register callbacks on the object returned by the ajax call. The ajax call returns a Deferred object which implements the Promise interface. Since its a promise, you can attach handlers to it so that when the request completes, the future handlers would be invoked.

Not only that, as you see in the example, we were also able to attach a new success handler (the argument to the done function) at a later point of time. If, until reaching that line, the ajax request has not been completed, in that case the function will be queued and will be invoked at a later point of time. If the request is already complete, then the function will fire immediately in case of success. In a similar way, you can add multiple functions as a to the failure queue as well.

This approach gives you a lot of flexibility in writing code and also makes the code more legible. The Deferred object has a number of other functions. One of them that is pretty interesting is the when() function. This function has a lot of appeal because it allows you to group together deferred objects and then set up future handlers after all the objects have been resolved or rejected.

This opens up the door to create interfaces that depend upon input from a number of sources but need to be rendered together. For example, a certain div contains information based upon a user choice. And the user selection leads to the firing of 2 different ajax requests. If your data is of the nature that the information would make sense only if the data from both the requests are shown together, then using the when function becomes a perfect candidate for such scenarios.

In the below example, you can issue a request to two different url's and only after the data from both the url's is retrieved, the future function that is specified as the argument to 'done' will be invoked.

$.when($.ajax("mypage1.html"), $.ajax("mypage2.html")).done(function(a1,  a2){
     $('div.page1details').html(a1[0]);
     $('div.page1details').html(a2[0]);
  });

Observe that there are 2 function calls inside the when function. You can have as many functions as you may like. The only criteria is that the object returned from the function call should be either a Promise or a Deferred. If it is a promise, then well and fine, If it is a Deferred, the promise() function is invoked and the Promise object is retrieved from the deferred object. A parent Deferred object is created and this parent object keeps track of all the deferred objects of the functions defined inside the when function. Once all the functions declared inside the when function resolved, the done function will be invoked. However if any of the functions declared in the when function fails, the failure callbacks are invoked without waiting for the resolution or rejection of the remaining functions.

You can easily use the done and the fail functions to register future callbacks for a deferred object. Another way you can do the same would be to make use of the then function. If you make use of the when - then function combination, it becomes much more easier to read the code, grammatically, because it appears to form some kind of a sentence. Lets see an example of using the when-then pair, and this time we shall also see how one can register multiple callbacks

$(function(){
    
    function fun1(data1, data2){
        console.log("fun1 : " + data1[0].query + " " + 
                    data1[0].results.length);
        console.log("fun1 : " + data2[0].query + " " + 
                    data2[0].results.length);
    }
    
    function fun2(data1, data2){
        console.log("fun1 : " + data1[0].query + " " + 
                    data1[0].results.length);
        console.log("fun1 : " + data2[0].query + " " + 
                    data2[0].results.length);    }
    
    function fun3(data){
        console.log("fun3 called upon faliure");
    }
    
    function fun4(data){
        console.log("fun4 called upon faliure");
    }    
    
    var successFunctions = [fun1, fun2];
    var failureFunctions = [fun3, fun4];
    
    $.when(
        $.ajax("http://search.twitter.com/search.json", {
            data: {
                q: 'jquery'
            },
            dataType: 'jsonp'
        })
    , 
        $.ajax("http://search.twitter.com/search.json", {
            data: {
                q: 'blogger'
            },
            dataType: 'jsonp'
        })
    ).then(successFunctions,failureFunctions);
    
});

In the above example, I created 4 functions. 2 of them will be invoked upon success and 2 will be invoked upon failure. As you can see, instead of passing a single function as a parameter to the then(), I passed in an array of functions for the success as well as the failure callbacks. Another point to be noted here is that since we have 2 ajax requests in the when() function, the success and failure methods can accept 2 arguments. Each argument will contain the jqXhr object that was returned by the corresponding ajax call. So, the above example demonstrates how to use multiple callbacks, and and also how to use the data that is obtained from a json ajax request. 

You may also note that since the ajax functions are making an JSONP request, I have referenced the JSON object in the success callbacks using data1[0] and data2[0] respectively. The data1 and data2 objects are actually arrays of the form [JSONObject, "success",jqXHR]. In case the request was an ordinary ajax request instead of a jsonp request, you would instead have to make use of the jqXHR object and retrieve the responseText as usual. 

Until now we have seen examples where the deferred object was the object that was returned by the ajax call. Although used extensively with ajax, the deferred object can be used in other places as well. An example of that would be running future callbacks after a set of animations have finished executing. You can group together the animations in a when() function and then using a deferred object, you can easily invoke future callbacks using the then() function. 

The catch here is that animations do not return deferred objects. They always return a simple jQuery object. This is where the Deferred constructor comes to the rescue. The deferred constructor can be used to create a new deferred object and then you can wrap your custom code inside the deferred object and return the wrapper instead of the jQuery object. 

Lets see an example for the same. In the following code, we are going to issue an ajax request, and resize a div. After both these tasks are complete, we are going to display the content retrieved via ajax in the div.

$(function(){

    function successFunction(data){
        console.log("successfunction");
        $('div.animateMe').html('Results : ' + data[0].results.length);
    }    
    
    function animateDiv(){
        //As per the documentation, the argument that is passed
        //to the constructor is the newly created deferred object
        var dfd = $.Deferred(function(dfd){
            $('div.animateMe').animate({height:'200px'},2000,dfd.resolve);
        });
        
        return dfd;
    }
    
    function failureFunction(){
        console.log("failureFunction");
    }
    
    $.when(
        $.ajax("http://search.twitter.com/search.json", {
            data: {
                q: 'jquery'
            },
            dataType: 'jsonp'
        }),animateDiv()
    ).then(successFunction,failureFunction);
});

You can also see a jsfiddle here

This example is pretty simple because it does nothing but wait for the ajax request as well as the animation to complete before resolving the Deferred object. The main point of interest of this example is the creation of a Deferred object within the animateDiv function. Within this function, we first create a new Deferred object. The constructor of the Deferred object takes a function as a parameter which is invoked just before the constructor is about to return. This function is passed the newly created Deferred object as an argument. Within, this function, we did an animate and upon animation completion, we indicated that the framework resolve the diferred object by passing the resolve function of the diferred object as an argument. In the next line, we simply return the newly created Deferred object.

In the above example, you might have noticed that we made use of a 'resolve' function. This function allows you to explicitly resolve a deferred object. While the ability to resolve a deferred object programmatically is desirable for non ajax requests, the same cannot be said for ajax requests. That's because an ajax request is said to be resolved only when a response is received from the server. So the resolution of an ajax request takes place internally in the ajax function and should not be available to the programmer directly. 

For non ajax requests, as in the example above, the programmer can resolve the deferred object based upon the specific requirement. Since it is the deferred object that has the resolve method and a number of other additional methods, the ajax request actually returns a Promise. This lets you invoke only the methods of the promise interface on the resultant object thereby preventing an explicit invocation of a programmatic resolve before the ajax request actually completes. 

When you are creating an non ajax deferred object, there is another method - reject, which indicates a failure and invokes the functions of the failure queue. Without any doubt, the deferred object has small but useful api. 

Summing It Up

  1. The deferred object in jQuery is an implementation of the Promise specification. This means that you can a promise where-ever a Deferred can be used. 
  2. Future handlers can be added to promise objects. 
  3. The future handlers are invoked upon the resolution(success) or the rejection(failure) of the invoked function. You can attach multiple future handlers to any deferred object. 
  4. The future handlers can receive parameters, which represent the information that was used to resolve the deferred object. 
  5. Non ajax objects can be wrapped in a deferred object by making use of the Deferred constructor. 
  6. The jQuery.when method can be used to group together a number of deferred objects and to attach future handlers to them. 
  7. Success handlers using the when function are invoked once all the deferred objects are resolved. 
  8. Failure handlers using the when function are invoked if any of the deferred object fails irrespective of the status of the remaining deferred objects.


I hope this article has been helpful in helping you gain an understanding of the concept of Promise and Deferred objects. There are a couple of links below that have good explanations of the same. I suggest that you take a look at them too if you still only have a vague idea of things. 

Also, there might be a thing or two, that I might have misunderstood! Make sure you point them out! 
Many thanks :)

Links and References


And then the most important of them all



Happy Programming :)

Signing Off
Ryan

1 comment:

Marco said...

Very useful, thanks a lot.