Not too long ago I came across jQuery deferred (even though it was added already in JQuery 1.5) and I immediately liked it. I feel this feature brings the web application development a bit closer to the desktop development.
Deferreds simply let you add multiple callback to long lasting operation calls like Ajax requests.
Long lasting operations without JQuery Deferred
If you haven’t used deferreds, this is probably how you had done long lasting actions like ajax calls.
$.get("/myservice/x/1",function(data,status){alert ('example')})
Long lasting operations like ajax require a callback function.
In this case the callback is given as a parameter to the caller.
This is fine, but there is a way to add a callback separately from the call.
var myPromise = $.get("/myservice/x/1"); myPromise.done(function(data,status){alert ('ok')}) myPromise.fail(function(data,status){alert ('failed')})
This is actually the simplest example of using Deferreds.
JQuery ajax methods (get,post,ajax…) return an object which you can add callback methods to it.
Why is it better?
Simply because it lets you keep the logic of execution and the logic of the result separated. This makes a more readable and maintainable code.
So what is a ‘Deferred’?
Deferred is an object which lets you add multiple callback methods to it.
It also triggering events which determine the state of the call, using it will determine which callback methods will be triggered.
In the Ajax example above the 2 callbacks are ‘done’ and ‘fail’ which are triggered according to the state of the Ajax call.
This list shows which events will trigger which callbacks.
Callbacks Events→ ↓ |
deferred.always() | deferred.done() | deferred.fail() | deferred.progress() |
deferred.notify() | Triggered | |||
deferred.notifyWith() | Triggered | |||
deferred.reject() | Triggered | Triggered | ||
deferred.rejectWith() | Triggered | Triggered | ||
deferred.resolve() | Triggered | Triggered | ||
deferred.resolveWith() | Triggered | Triggered |
What is a ‘Promise’?
A Promise is an object which is given from the Deferred and may be used just to add callbacks.
The 2 objects, Deferred and Promise, give us the separation in the logic of the caller and the logic of the result.
For example, in the ajax calls of JQuery, the ajax call creates a Deferred and also a Promise from that Deferred. The Deferred is used to trigger events and the Promise is returned to the user so he will add callback functions to it.
Create your own Deferreds functions
You also can use Deferreds in your function much like the ajax functions are using them.
These are the steps:
1. Create a Deffered inside your function.
2. Use the Deffered to trigger events.
3. Return the Promise object.
See this simple example:
myLongLastingOperation = function(time) { var myDeferred = $.Deferred(); setTimeout(myDeferred.resolve, time); return myDeferred.promise(); } myPromise = myLongLastingOperation(1000); myPromise.done(function(){alert('ok')}) myPromise.fail(function(){alert('failed')})
In the example above I have created a function similar to the what ajax function is doing.
I have created a Deferred, used that Deferred to trigger an event by using the resovle() function once the operation was done, and returned the Promise which I created from that Deferred.
The returned promise is then used for adding callbacks.
Progress
resolve() and reject() are both events that determine the status of the result of the operation.
But there is another event you can trigger – notify().
This event which was added in version 1.7 can be triggered if you have a process and you want to trigger a callback while the process is still running.
myLongLastingOperation = function(time,numOfCount) { var myDeferred = $.Deferred(); function wait(counter){ if(counter<numOfCount){ myDeferred.notify(); setTimeout(function(){wait(counter+1)}, time); } else{ myDeferred.resolve(); } } wait(0); return myDeferred.promise(); } myPromise = myLongLastingOperation(100, 3); myPromise.progress(function(){document.write('tick')}); myPromise.done(function(){document.write('done')});
Not impressed yet? Meet ‘when’
We saw that you can add multiple callbacks to Deferreds and Promises.
‘when’ lets you do the same thing but it can do it for multiple Deferreds all at once.
myLongLastingOperation = function(time) { var myDeferred = $.Deferred(); setTimeout(function(){myDeferred.resolve();}, time); return myDeferred.promise(); } myPromise = $.when( myLongLastingOperation(500), myLongLastingOperation(1700)); myPromise.done(function(){document.write('done')});
In this case the done callback will be triggered only when the 2 processes are resolved.
This gives you the ability to synchronize between many long lasting processes, which can be useful for so many things.
For example, if you are loading many sources and you wish to do something when they are all done.
Changing the result with ‘then’
I mentioned that you can’t trigger events on a Promise, you can only add events to a Promise.
However, you still have a way to control the result in another way; by adding filters to the Promise which will be able to alter the response from the caller.
Since jQuery 1.8 this is done using the ‘then‘ command:
var myPromise = $.get('non_exiting.php'); myPromise = myPromise.then( function(){return $.Deferred().resolve("ok")}, function(){return $.Deferred().resolve("ajax failed but still we pass")}); myPromise.done(function(message){document.write('done: '+message);}); myPromise.fail(function(message){document.write('error: '+message);});
This example shows that even though the deferred(the ajax) failed, the filter changed the result to resolve, causing the ‘done’ callback to be triggered instead of ‘fail’.
Notice that ‘then’ returns also a promise, this makes it a chainable command, which means that you can chain a more than one filter one after the other.
*Notice, before version 1.8 you would do this using the pipe command, but since 1.8 it is done using ‘then‘.
‘then’ was originally added in version 1.5 as a callback which is triggered on every event.
But since version 1.8 it changed it’s purpose to be a filtering chainable method which can effect the state of the result.
This is why you can still see examples on the web that do it the old way.
Adding a callbacks after event was triggered
You should know that you can add callback functions to a promise even if the Deferred was already resolved or failed.
This means that if you add a callback method to an ajax call for example, that already returned, the callback would immediately be triggered.
This small and seemingly unimportant feature is actually quite useful, because you don’t have to worry that the Deferred is finished when adding callbacks. More over, it lets you add many callbacks whenever you like and by so, reuse deferreds.
A good example for this use would be a cache of ajax calls, this is infact something I have implemented in my workplace(convertro.com) quite successfully.
I work with jQuery everyday and didn’t know this. Thanks!