Exploring Deferred and Promise in JQuery

by:

Web Development


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 concept of Promises. To understand all about Deferred objects, it would be 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 on an anticipated outcome. All we know for sure is that we are going to get an outcome. Let’s just say that we wrap the term “outcome” in a fancy-looking gift wrapper and call it a “Promise.”

Defining Promises

So, what is a Promise? Well, it’s nothing more than an object returned by a method based upon which you can determine a future course of action. Let’s 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 don’t. But you need to have a plan no matter what the outcome of the interview is. And you need that plan now. You cannot plan it for 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, 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

);

What is interesting about this is that, even though you don’t 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, is the promise object 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 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 or 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 in time, and the amount of time required for the completion of an Ajax request cannot be determined beforehand.

Deferred Objects in jQuery

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 while also letting you create new Deferred objects and attach future handlers to them and resolve them programmatically.

Let’s take a look at the jQuery Deferred object in terms of an Ajax request.

In versions of jQuery prior to 1.5, to attach a success or failure handler to an Ajax request, one would have declared 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, 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 it’s a progressive change because it adds more effectiveness to the Ajax functionality and more functionality to the framework as a whole. 

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 can 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 it’s a promise, you can attach handlers to it so that when the request completes, the future handlers would be invoked.

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

A Closer Look at Deferred Object Functions

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 lets you 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 creating interfaces that depend on input from a number of sources but need to be rendered together. For example, a certain div contains information based on a user’s choice, and the user selection leads to the firing of two 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, using the when function becomes a perfect candidate for such scenarios.

In the below example, you can issue a request to two different URLs. Only after the data from both the URLs is retrieved is the future function that is specified as the argument to done 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 two 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 are resolved, the done function is 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 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 easier to read the code grammatically because it appears to form some kind of sentence. Let’s 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(" 
            data: 
                q: 'jquery'
            ,
            dataType: 'jsonp'
        )
    ,
        $.ajax(" 
            data: 
                q: 'blogger'
            ,
            dataType: 'jsonp'
        )
    ).then(successFunctions,failureFunctions);
     
);

In the above example, I created four functions. Two of them will be invoked upon success and two 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 two Ajax requests in the when() function, the success and failure methods can accept two 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 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 the case where the request was an ordinary Ajax request instead of a JSONP request, you would have to make use of the jqXHR object and retrieve the responseText as usual. 

Using Deferred Objects In Other Ways

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. 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 you can wrap your custom code inside the Deferred object and return the wrapper instead of the jQuery object. 

Let’s see an example. 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(" 
            data: 
                q: 'jquery'
            ,
            dataType: 'jsonp'
        ),animateDiv()
    ).then(successFunction,failureFunction);
);

You can also take a look at a jsfiddle.

This example is pretty simple because it does nothing but wait for the Ajax request and the animation to complete before resolving the Deferred object. The main point of interest in 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 passes the newly created Deferred object as an argument. Within this function, we did an animate, and upon animation completion, we indicated that the framework resolves the Deferred object by passing the resolve function of the Deferred 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 lets you 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 on 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 a 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 a small, but useful API. 

Summing Everything Up

  1. The Deferred object in jQuery is an implementation of the Promise specification. This means you can use a promise wherever 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 regardless 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’ve misunderstood! Make sure you point them out! 

Many thanks! 

Links and References





Source link

Leave a Reply

Your email address will not be published.