On Github alexmcpherson / reveal-deferreds
var dfd = $.Deferred(); dfd.state(); // 'pending'
var dfd1 = $.Deferred(); // 'pending' dfd1.reject(); // 'rejected' dfd2 = $.Deferred(); // 'pending' dfd2.resolve(); // 'resolved'
ONE WAY. Once you leave 'pending', there is no going back, and no changing your mind
Rejected is always rejected, and resolved is always resolved
Changing the state of something isn't that useful by itself, you could just set it as a variable
The killer feature of deferreds is that you can listen to state changes
dfd.done(function() { console.log('This deferred was -resolved-') }); dfd.fail(function() { console.log('This deferred was -rejected-') }); dfd.always(function() { console.log('This deferred was either rejected or resolved') });
Calling .reject() or .resolve() will change the state, triggering a callback. This is similar to $.ajax and .success(), .error(), and .complete()
Other useful methods:
dfd.progress( fn ) dfd.notify() //triggers callback, no state change dfd.then( resolveFn, rejectFn, notifyFn ) $.when( dfds* ) dfd.promise()
Summary:
.reject() --> .fail( fn ) --> .state() == 'rejected'
.resolve() --> .done( fn ) --> .state() == 'resolved'
.notify() --> .progress( fn ) --> .state() == 'pending'
A deferred might not immediately appear useful
I will show a common task, and an elegant way to solve it using a deferred.
Task: Wait for several ajax calls to complete before doing something
Potential solution:
$.ajax({ success: })
Task: Wait for several ajax calls to complete before doing something
Potential solution:
$.ajax({ success: function() { $.ajax({ success: function() { } }) } })
Task: Wait for several ajax calls to complete before doing something
Potential solution:
$.ajax({ success: function() { $.ajax({ success: function() { $.ajax({ //PLEASE don't do this }) } }) } })
Another attempt, saving state externally:
var completedCalls = []; $.ajax({ success: function() { completedCalls.push('facebookData'); checkAllCompleted(); } }); $.ajax({ success: function() { completedCalls.push('twitterData'); checkAllCompleted(); } }); var checkAllCompleted = function() { //Check completedCalls array for all 3 values }
There's a better way!
var getAddressData = function() { return $.ajax({ //ajax code here }) };
var getAddressData = function() {...}; var getTwitterData = function() {...}; var getFacebookData = function() {...}; //all 3 return promises (deferreds you can't reject/resolve yourself)
var getAddressData = function() {...}; var getTwitterData = function() {...}; var getFacebookData = function() {...}; $.when( getAddressData(), getTwitterData(), getFacebookData() ).done(function() { // do something with all that data })
$.when accepts many promises or deferreds and represents them as one.
This means that if one is rejected, the whole 'bundle' is rejected immediately.
Task: You are rendering different views on a single page application. Some need external data, others don't.
var aboutPage = { init: function() { this.promise = this.getInfo() //ajax call } }
var aboutPage = {...} var contactPage = { init: function() { // I don't need to fetch data here. // I want to provide the // same interface to my object. } }
var aboutPage = {...} var contactPage = { init: function() { this.promise = $.Deferred().resolve(); } }
var aboutPage = {...} var contactPage = {...} var changePage = function(page) { $.when(page.promise).done(function() { page.render(); }); }
var aboutPage = {...} var contactPage = { init: function() { // this.promise = $.Deferred().resolve(); // This does the same thing! // $.when(undefined) resolves immediately } }
$.when lets you treat any object that isn't a deferred like a deferred.
This lets you sometimes use deferreds, and other times not, but treat the objects in the same manner.
Task: You have to minimize the number of API calls you make in an application
var UserDetails = { cache: {}, search: function(user_name) { if (!this.cache[user_name]) { this.cache[user_name] = $.getJSON('www.mysite.com/?q=' + user_name); } return this.cache[username]; } }
UserDetails.search('bill_smith').done(function(results) { //made a round trip to the server console.log(results); }) //After some usage: UserDetails.cache.bill_smith //Promise from $.getJSON UserDetails.cache.joe_taylor //Promise from $.getJSON
How is that useful?
UserDetails.search('bill_smith').done(function(results) { // Did NOT make a round trip to the server this time // Deferred passes the same params from the original callback console.log(results); })
This trick counts on the fact that you resolve a deferred with parameters, and that those get passed to any callback attached to the same promise
This lets you store both the state of a request (failed, resolved) as well as the data it returned in the same object.
Task: Write literate, pretty javascript
Understandable code is maintainable code
setTimout(function() { flashBanner(); }, 500);
$.wait = function(duration) { // The deferred constructor can take an argument return $.Deferred(function(def) { setTimeout(def.resolve, duration); }); }
//Pretty! Readable! $.wait(500).then(flashBanner);
Another example of readable, maintainable code:
$.when( doSearch('twitter'), // These must return promises doSearch('email'), doSearch('weather') ).done(function(twitter, email, weather) { renderHomePage(twitter, email, weather); });
Defereds have already done a good job having natural-language syntax, but you can extend that
Write methods that return promises!
You have a hotel booking site with long searches. Keep your user updated!
Your search takes 3 steps: Geocoding, hotel search, and pricing search.
geoCode('Moscow'); //returns locale_id hotelSearch(421124); //returns array of hotels priceSearch([23, 343, 6653, 23463]); //returns prices by hotel
How can you update a progress meter if it takes 20 seconds to search?
var searchDfd = $.Deferred(); searchDfd.progress( updateProgressBar ); searchDfd.done( showResultsPage ); geoCode('Moscow', searchDfd); // When this completes, it calls the next search // as well as searchDfd.notify('geocodeComplete');
Each stage of the search calls .notify() as it completes, and the final search calls .resolve() to finish the search.
Demo!How can you orchestrate many simultaneous animations to make sure they run on time?
These all attach a promise to their DOM node
How can you orchestrate many simultaneous animations to make sure they run on time?
var myBanner = $('h1'); myBanner.hide(2000); myBanner.promise().done(function() { console.log("It's disappeared!"); })
How can you orchestrate many simultaneous animations to make sure they run on time?
var complexRemove = function() { var defs = []; defs.push( $('h1').delay(Math.random()*5000).fadeOut() ); defs.push( $('div').fadeIn(100).delay(200).slideDown() ); //makes an array of defferds one deferred return $.when.apply(null, defs); } complexRemove().done(function() { window.close(); });
Syncing multiple animations can be hard withuot deferreds.
Call node.promise() to start working with them