Thursday, November 29, 2012

Asynchronous Javascript & jQuery Deferred Why?

Using jQuery Deferred
In this post I'll try to cover an overview of promises and deferred. Further how to use them in nodejs.

Define Deferred?
Google Define: Put off (an action or event) to a later time; postpone

Deferred in javascript?
Javsacript being an event driven scripting [and a developing programming] language, there is obvious need for developers to defer things awaiting events.
For ex:
window.addEventListner("load", function () { console.log("Defer my execution till window load"); }, false);
$.ajax({ url: 'data.html',
     success: function () {
       console.log("Defer my execution till ajax load is successful")
     },
     error: function () {
       console.log("Defer my execution till ajax load fails")
     }
});

So? This is nothing unusual!!!
Ya, obviously it is nothing unusual and obviously a beginner level fact.
Deferred: As a programming paradigm, is a design pattern which solves the problem of code neatness and maintainability while travelling along the lane of languages like javascript.

Why code neatness gets spoiled?
3 Basic Scenarios on which I feel so :D are
1. Duplicate code on either success or error callbacks [Ex: Hiding the loader animation in ajax]
2. Start a deferred event after success of another deferred event [Means: Call backs inside Call backs inside .. recursion :P]
3. Awaiting multiple events to complete successfully to proceed with someother execution [Ex: Submit button clicked, Animation Completed, Validation Done -> Proceed to Sign UP]

Beginner's Solution for all the above:

Scenario:1
//Assume the beginner knows a bit of refactoring :P
function common() {
 //Common Function after ajax call
}
$.ajax({ url: 'data.html',
     success: function (data) {
       common();
       console.log("Defer my execution till ajax load is successful")
     },
     error: function (data) {
       common();
       console.log("Defer my execution till ajax load fails")
     }
});

Scenario:2
$.ajax({ url: 'data.html',
     success: function (data) {
       $.ajax({ url: 'data1.html',
         data: data,
         success: function (data1) {
               $.ajax( ..... //so on and it is enough :P
             }
             });
     } 
    });



Scenario:3
//I Couldn't think of any better worse code sample :P
var i = 0;
function incr() {
 i++;
 if( i == 2 ) {
  doSubmitForm();
 }
}
$("submit-button").on('click', fuction () { 
 //do some validation and increment a global var 
});
$("submit-button").on('click', function () { 
 //do some animation and increment a global var 
});

And Hence we are done with code samples. Now lets go with Deferred and solve all the above 3

Scenario:1
var ajaxCall = $.ajax(myResourceURL); // Returns a deferred
ajaxCall.done(function () { /* Call me on success */ });
ajaxCall.fail(function () { /* Call me on success */ });
ajaxCall.always(function () { /* Call me always. Plz shut the loader div :P */ });


Scenario:2
var one = $.ajax( url ),
    two = one.pipe(function( data ) {
      return $.ajax( url2, {data: data} );
    }),
    three = two.pipe(function( data ) {
      return $.ajax( url3, { data: { data: data } } );
    });
three.done(function( data ) {
  // data retrieved from url2 as provided by the first request
});

Scenario:3
var validate = new $.Deferred(),
    animate = new $.Deferred(),
    proceed;
validate.done(function () {  });
animate.done(function () { });
proceed = $.when(validate, animate)
           .then(proceedSubmit, dont, notifySomething);
$("submit-button").on('click', function () { /*do some validation and you can even reject*/ validate.resolve(); });
$("submit-button").on('click', function () { /*do some animation and resolve or reject */ animate.resolve(); });

PS: Refer when, pipe and then
Note:
1. How does promise differ from deferred?
Promise can't be resolved or rejected by another source. But deffered can be. [Ex: In listing 3 validate and animate are resolved from click event's callback]
2. How do I pass params to done, fail, always callbacks or change the context of execution?
Check resolveWith and rejectWith
Also you can do deferred.resolve.call(context, param1, param2,...) incase you wanna get rid of array processing
3. Are we by any chance restricted to one call back per promise or deferred state (done, fail, always)?
Nopes (at least in jQuery deferred) you can add as many listeners [same as event listeners] for any of the promise states. All such callbacks will be triggered when promise gets resolved to appropriate state. This feature makes really good point in decoupling the actions we use to do in only available success or error callback in response to an event [like ajax load].
4. What if I attach a callback for a deferred which is already resolved?
The callback will be triggered immediately after the attachment, provided the state of the promise matches.

Lets see the way we can use jQuery deferred in nodejs
As mentioned in the Note 1: We can't use promise in node unless the module supports resolve & reject with in itself
var $ = require('jquery'),
    fileDeferred = new $.Deferred(),
    fs = require('fs');

fileDeferred.done(function (data) {  });
fileDeferred.fail(funtion (err) { });
fileDeferred.always(function () { });

fs.readFile(filename, 'utf8', function (err, data) {
 if(err) {
  fileDeferred.reject.call(null, err);
 } else {
  fileDeferred.resolve.call(null, data);
 }
});
If you do lots async programming do consider having a look at asyncjs
I hope you will be able to see the difference in normal way of programming than deferred way. Also how deferred way helps you to maintain code neatness. If you don't program the deferred way, I think its time to give it a try :)
Loads of thanks to @trevorburnham for Async Javascript
This blog is almost a gist of what I learnt from his creation :)
I hope I did some justice and helped you understanding the need for deferred.