In 2009, I was working as a student for a small consulting firm, tasked to develop some real-time visualization stuff for the web.
Normally, this would have meant writing some Flash. But it was 2009, so Flash Is Dead and HTML5 Is King.
It was the first project I would work on where Javascript would make up the meat of the work, rather than just “sprinkling some jQuery on it” – another thing people said back then.
Over the next 8 months, I travelled down the path of re-learning Javascript. It’s likely a path many of you are familiar with.
At first, I tried to make everything object-oriented. Then, oh god, so much global state. I sat there, puzzled, googling desperately:
I eventually realized that I was among the many who never became familiar with the slumbering beast in our browsers, soon to be awoken to bring on the next generation of the web.
Despite this enlightenment, something kept coming back to haunt every project I worked on. See, the difficulty was not using the new shiny HTML5 canvas APIs to draw spinning circles, but rather, it was this pesky AJAX thing.
This is genuinely how I expected things would work.
But it isn’t. Instead, we have callbacks. Mysterious functions which are called when our request completes (if ever) and passes the resulting data as arguments.
Callback Hell is the affectionate name given to what happens when you want to do a bunch of sequential things using these asynchronous functions.
Let’s use The Echo Nest as our guinea pig. These guys are really cool and let you upload songs to be analyzed. With their analysis, you can create some really effingamazing stuff.
But that doesn’t necessarily look that cool or read that cool.
$.getJSON(url,{id:trackID,api_key:apiKey},function(data){varanalysisURL=data.response.track.audio_summary.analysis_url;track=data.response.track;// This call is proxied through the yahoo query engine.// This is temporary, but works.$.getJSON("http://query.yahooapis.com/v1/public/yql",{q:"select * from json where url=\""+analysisURL+"\"",format:"json"},function(data){if(data.query.results!=null){track.analysis=data.query.results.json;remixer.remixTrack(track,trackURL,callback);}else{console.log('error','No analysis data returned: sorry!');}});});
Everything is fun and games until you have callbacks calling callbacks calling callb…backs on backs on backs.
Deferral
Luckily, the Javascript community is filled with smart, hardworking people who make tools to fix these problems. In 2011, a group of people whom I respect dearly were really excited to talk about how the antidote had arrived.
Our Virgil is here, ready to guide us out of this place, and his name is $.Deferred.
Breaking It Down
Promises are by no means a new idea. They were invented back in the 1970s by some really smart guys who were trying to figure out how to write programs that did more than one thing at a time. Even though Javascript is single threaded, promises prove to be extrodinarily useful for managing asynchronous calls.
A promise is like saying that you’re doing something and you don’t know how long it will take but, when you’re done, you promise you’ll do some other stuff. In reality, it’s much like me promising that I will remember to pick up some milk after work, except computers don’t break promises.
In this example, we can see that we now actually doing something asynchronous, namely waiting for 5s.
The key though is that, once we’re done waiting, we’re going to call dfd.resolve('hello world').
By resolving the deferred object, we are fulfilling the promise we made. We can also use reject to say that we failed to fulfill the promise (computers sometimes forget to pick up milk, after all). Finally, we can also use notify to inform everyone how we are progressing.
Everything is Already a Promise
Anything that is done asychronously in jQuery is already re-written to return a promise.
This is better, it definitely makes it clearer what exactly is going on. The only magic here is what are these success and error things? It turns out that they are actually just deferreds.
In this simple example, we can see that we are going to try and an item with id = 5 and then either log the data (on success) or complain about errors (on error).
Working in Parallel
Let’s say that you have a couple things you want to do at the same time and then maybe when they are both done you want to do something else.
$.when will create a wrapper deferred that tracks the results of each deferred. If any of the promises fail, it will call reject on the master promise, otherwise it will call resolve.
There are many more functions at your disposal, check out the $.Deferred documentation for more information.
Cooking with Deferreds
Wait
I really like this helper function as it allows you to do deferred waits when you’re doing stuff like long polling. I’m not sure who the credit for this belongs to, it might have been from an SO post or maybe I wrote it myself. If you know, please leave a comment with its source.