chaining asynchronouse requests
Lots of times I find myself making a bunch of async things like ajax requests, one after the other. Where the result of the first one is required in the second one. For e.g.
It usually looks like this:
This right moving code can very quickly become:
- annoying
- not pleasent to write
- hard to maintain
- and hard to debug in
- plus it’s killing my 80 char word wrap column
- more stacks than one may like
A better solution: jQuery’s Deferred Concept
Let’s start by using the deferred object to re-write the code we had above which had functions wrapped inside the success callbacks.
Ajax methods by default in jQuery are returning a promise
of a deferred
object which is created withe every ajax request. jQuery is also internally calling resolve
on the deferred
object when the ajax is successfully and calling reject
when the ajax method fails. This updates the state on the promise and fires our callbacks attached to it via the then
method.
The thing to note here is that then
calls the next function only after the first functions promise has been resolved.
So jQuery did a lot of thing’s here for us when we used the ajax method. But promises in jquery are not limited to ajax and can be used anywhere you want. Below you will see how.
Exploring jQuery’s Deferred Object
jQuery provides a deferred object which can be created by using its factory method $.Deferred([beforeStart])
. The deferred object has promise
which can be returned when you the value
is unknown and you want to return something so the receiver can proceed and call the next based upon its value. This is done by using the then
method available on the promise
. The first method which returned the deferred object can call resolve
on the deferred object when the value is known and at that time the methods waiting on its promise will execute. Lets see an example.
We want to increase the height of a div from 100 to 500 and then let the next function know to put content in the div. The content should only be placed after the the width has increased.
Lets go over the API’s available in the jQuery Deferred Object.
-
$.Deferred([beginMethod]): Factory method to create the deferred object in its initial state of pending
-
deferredObj.resolve(args): This moves the state of the promise to resolved and thus causes the
done
listeners on the promise to fire. It passes args to the ha__ndler. The args could represent thevalue
which was pending and is now available. -
deferredObj.reject(args): This moves the state of the promise to rejected and thus causes the
fail
listeners on the promise to fire. It passes args to the handler. -
deferredObj.notify(args): This does not change the state but causes the
progress
listeners on the promise to fire. It passes args to the handler. -
deferredObj.state(): This returns the state of the promise/deferred object. It could be pending, resolved or rejected
-
deferredObj.promise(): gives an object which can be returned and observed for when the
value
is available -
promiseObject.then(df,[ff],[pf]): takes methods to be called when the promise is resolved, rejected or has progress
-
promiseObject.done(df): takes method to be called when the promise is resolved
-
promiseObject.always(af): takes method to be called when the promise is either resolved, rejected or has progress
-
promiseObject.fail(ff): takes method to be called when the promise has failed
-
promiseObject.progress(pf): takes method to be called when the promise has progress
-
$.when(promise, [promise+]): returns a
promise
which changes state only when all the promises passed in resolve.
The deferredObj
and promiseObject
object are the same thing besides the subtle difference that on the promise
object one can not call resolve
, reject
, notify
etc. Therefore the promise
object is only good for listening and thus an excellent candidate to be returned by the original method. Now all methods can be called on the deferred
object but you should return the promise
object and add callbacks on it. So in short: Change state of deferred and listen for state change on promise.
The when
factory method is excellent choice when you want to create a new promise which is a combination of other promises
. For e.g.
Now sometimes, things might finish up and faster than you expect. Meaning the state on a deferred will move from pending to resolved even before you get an opportunity to add an event handler on the promise of that deferred object. This is non-issue here because any handler added after the state has changed would fire immediately if its state had already been reached. For e.g.
References: