Friday, 8 March 2013

Understanding Promise Pattern in JavaScript Using jQuery’s Deferred Object

Asynchronous programming is used almost everywhere these days. JavaScript is no exception. In rich JavaScript applications, we have to perform several tasks like calling a service using AJAX, caching data, applying animations and some new cases might arise while writing the application.

Irrespective of whether an operation is synchronous or asynchronous, we need a response from the when the operation is succeeded or even when it fails. Synchronous operations are usually enclosed within try…catch blocks to check for errors. As asynchronous operations run in background and we can’t determine when they will complete, their failures cannot be caught by the catch block. Even in case of successful completion, the response won’t be available for the immediate next statement. These cases are handled using callbacks.

We see several patterns in which callbacks are registered. While using raw AJAX invocations with XMLHttpRequest, we assign the callback to onreadystatechange property of the object, as shown below:

var xhr = new XMLHttpRequest();
xhr.open(“GET”,url, true);
xhr.send();

xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
        if(xhr.status == 200){
     successCallback(xhr.responseText);
        }
        else{
            failureCallback(xhr.responseText);
        }
    }
};

We also see the cases where the asynchronous function accepts a callback function as a parameter.
invokingAsyncFunction(successCallback(data), failureCallback(error));

These callbacks look good unless we have a chain of three or more asynchronous calls. When the number goes up, it becomes difficult to handle them and the code becomes less readable.

We can overcome this difficulty using the Promise pattern. The Promise pattern believes in returning a deferred object that can be used to call a piece of logic when the underlying asynchronous operation is either resolved or rejected. In other words, we can say the pattern promises us that it would send a result when it gets. The result can be data if the operation is successful or details of an error in case of failure.

The deferred object will hold the current state of the asynchronous operation. At any given time, the deferred object will have one of the three states: unresolved, resolved and rejected.

The deferred object would be in unresolved state when it is waiting for the asynchronous operation to complete. Once the operation is completed successfully, it enters resolved state. Otherwise, it enters rejected state.

jQuery library added support of deferred in jQuery 1.5. The library itself uses this feature for implementation of AJAX features.

jQuery’s deferred object makes it easy to write asynchronous code in JavaScript. Following steps must be followed while writing an asynchronous function using deferred object:

  1. Get a deferred object by calling the $.Deferred() constructor
  2. In case of success, call resolve function passing result of the operation
  3. In case of failure, call reject function passing the error details
  4. Return deferred’s promise object, which provides ways to attach callbacks and determine current state of the deferred object

With this understanding, let’s put the above AJAX logic in a function and make it asynchronous using jQuery’s deferred object:
function ajaxCallToAUrl(url){
  var deferred = $.Deferred();    //Getting a deferred object
  var xhr = new XMLHttpRequest();

  xhr.open(“GET”,url,true);
  xhr.send();

  xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
      if(xhr.status==200)
        deferred.resolve(xhr.responseText);  //Calling resolve() in case of success
      
      else
        deferred.reject(xhr.responseText);  //Calling reject() in case of failure
    }
  }
  return deferred.promise();  //Returning the promise object
}

Following are the steps to be followed while calling the above function:
  1. Call the function and store the returned promise in an object
  2. Attach a callback to done() function of promise. It will be called when the deferred object enters resolved state
  3. Attach a callback to fail() function of promise. It will be called when the deferred object enters rejected state

Following snippet demonstrates it:
var promise = ajaxCalToAUrl(url);
promise.done(function(data){
    //Update the UI to bind data obtained
}.fail(function(error){
    //Display the error message
});

The success and failure functions can also be attached to the promise object using then function as well. The then() function accepts three callbacks, first one is for success, second one is for failure and the third one is for handling progress. Failure and progress callbacks are optional. Progress callback is executed when deferred receives a progress notification. Above implementation can be expressed as follows using then function:
promise.then(function(data){
    //Update the UI to bind data obtained
}, function(error){
    //Display the error message
}, function(){
    //Notify user that the operation is in progress
});

Using then(), we can chain multiple operations as well. Following snippet demonstrates chaining:
var secondPromise = firstPromise.then(function(data){
    return callAnotherAsyncFunction(data);     //Assuming the function callAnotherAsyncFunction returns a promise
});

secondPromise.done(function(data){
    //Chained request is successful. Update UI with the data obtained
});
We can also combine multiple requests using $.when(). The when() function accepts multiple deferred objects. It returns a promise object. State of the when function changes to resolved when all the operations are successfully completed. If any of the operation is failed, state of when changes to rejected.
$.when(deferred1, deferred2).done(function(){
    //Logic to be executed on successful execution
}).fail(function(){
    //Logic to be executed upon failure of any of the operations
});

This is a beginning to using promises. jQuery's deferred object offers capabilities to forcefully reject the operation, notify change of status and some other features that we may require in an application.

References:


Documentation on jQuery’s official site about deferred objects
Article about deferred and promise on MSDN ScriptJunkie


Happy coding! 

3 comments:

  1. Thanks a lot. Very instructive.

    ReplyDelete
  2. Here is an alternative explanation: http://jingding.blogspot.com/2012/05/jquery-deferred-objects-promise.html

    ReplyDelete