We started this chapter by showing how the Ajax methods in jQuery 1.5+ ($.ajax, $.get, and $.post) return Promises. But to really understand Promises, we need to make a few of our own.
Let’s give the user a prompt to hit either Y or N. The first thing we’ll do is create an instance of $.Deferred that represents the user’s decision.
| var promptDeferred = new $.Deferred(); |
| promptDeferred.always(function(){ console.log('A choice was made:'); }); |
| promptDeferred.done(function(){ console.log('Starting game...'); }); |
| promptDeferred.fail(function(){ console.log('No game today.'); }); |
(Note: always is available only in jQuery 1.6+.)
You’re probably wondering why I created an instance of Deferred when this section is called “Making Promises.” Fear not—a Deferred is a Promise! More precisely, it’s a superset of Promise with one critical addition: you can trigger a Deferred directly. A pure Promise only lets you add more callbacks; someone else has to trigger them.
We can trigger our Deferred with the resolve and reject methods.
| $('#playGame').focus().on('keypress', function(e) { |
| var Y = 121, N = 110; |
| if (e.keyCode === Y) { |
| promptDeferred.resolve(); |
| } else if (e.keyCode === N) { |
| promptDeferred.reject(); |
| } else { |
| return false; // our Deferred remains pending |
| }; |
| }); |
You can see this example in action at http://jsfiddle.net/TrevorBurnham/PJ6Bf/. Load the page and hit Y. The console will say the following:
<= | A choice was made: |
| Starting game... |
You see what happened? When the Deferred was resolved, its always and done callbacks were run. (Not coincidentally, the callbacks were run in the order in which they were bound.)
Refresh the page and hit N.
<= | A choice was made: |
| No game today. |
So, when the Deferred was rejected, its always and fail callbacks were run. Note that callbacks always run in the order in which they were bound. If the always callback had been bound last, the order of the console output would be reversed.
Try hitting Y and N repeatedly. After the first choice is made, there’s no effect! That’s because a Promise can be resolved or rejected only once; after that, it’s inert. We say that a Promise is pending until it’s either resolved or rejected. You can find out whether a Promise is "pending", "resolved", or "rejected" by calling its state method. (state was added in jQuery 1.7; in earlier versions, use isResolved and isRejected.)
When you’re carrying out a one-shot async operation with two broad outcomes (e.g., success/failure or accept/decline), making a Deferred gives you an intuitive representation of it.
We just learned that a Deferred is a Promise. So, how do we get a Promise that isn’t a Deferred? Simple: call a Deferred’s promise method.
| var promptPromise = promptDeferred.promise(); |
promptPromise is just a copy of promptDeferred without the resolve/reject methods. It doesn’t matter whether we bind a callback to a Deferred or to its Promise, because they share the same callbacks internally. They also share the same state ("pending", "resolved", or "rejected"). This means that creating multiple Promises for a single Deferred would be pointless. In fact, jQuery will just give you the same object.
| var promise1 = promptDeferred.promise(); |
| var promise2 = promptDeferred.promise(); |
| console.log(promise1 === promise2); // true |
And calling promise on a pure Promise just gives you a reference to the same object.
| console.log(promise1 === promise1.promise()); // true |
The only reason to use the promise method is encapsulation. If we pass promptPromise around but keep promptDeferred to ourselves, we can rest assured that none of our callbacks will fire until we want them to fire.
To reiterate, every Deferred contains a Promise, and every Promise represents a Deferred. When you have a Deferred, you control its state. When you have a pure Promise, you can only read that state and attach callbacks.
I started the chapter with the example of Promises returned by jQuery’s Ajax functions ($.ajax, $.get, and $.post). Ajax is a perfect use case for Promises: every call to a remote server will either succeed or fail, and you’ll want to handle those cases differently. But Promises can be just as useful for local async operations, like animations.
In jQuery, you can pass a callback to any animation method to be notified when it’s finished.
| $('.error').fadeIn(afterErrorShown); |
In jQuery 1.6+, you can instead ask a jQuery object for a Promise, which represents the completion of its current and pending animations.
| var errorPromise = $('.error').fadeIn().promise(); |
| errorPromise.done(afterErrorShown); |
Animations applied to the same jQuery object are queued to run sequentially, and the Promise resolves only when all animations that were on the queue when you called promise were resolved. So, this generates two distinct Promises that will be resolved in sequence (or not at all, if stop is called first).
| var $flash = $('.flash'); |
| var showPromise = $flash.show(); |
| var hidePromise = $flash.hide(); |
Pretty simple, right? In jQuery 1.6 and 1.7, a promise on a jQuery object is just a convenience method. You could easily create an animation Promise with the same behavior yourself by using a Deferred’s resolve method as the animation callback.
| var slideUpDeferred = new $.Deferred(); |
| $('.menu').slideUp(slideUpDeferred.resolve); |
| var slideUpPromise = slideUpDeferred.promise(); |
In jQuery 1.8, released shortly before this book went to press, animation Promises have become much more powerful objects. The Promise has additional information attached, including props, the computed values that the animation is moving toward—very valuable for debugging. You can also get progress notifications (see Progress Notifications) and adjust the animation on the fly. Draft documentation for this new set of features can be found at https://gist.github.com/54829d408993526fe475.
jQuery 1.8 added one more source of Promises in jQuery: $.ready.promise() gives you a Promise that resolves when the document is ready. That means that these are now equivalent:
| $(onReady); |
| $(document).ready(onReady); |
| $.ready.promise().done(onReady); |
In this section, we’ve seen how you obtain jQuery Promises: either you create a $.Deferred instance, giving you a Promise that you control, or you make an API call that returns a Promise. In the next few sections, we’ll see what you can do with all those Promises.
3.141.7.186