Last year, I gave a small presentation on Promises in AngularJS, with an interactive element. It’s a little bit dated, and a million people have now written about Promises, so I’ll leave the basics and the crash course to Kris Kowal, and jump right to the part I thought was interesting: my examples. Very few people put their examples on JSFiddle for the audience to experiment with, to see what breaks – or doesn’t – when you make alterations.
Working on my resume and portfolio recently, I stumbled across this presentation I did about 18 months ago. I had completely forgotten about it or I would have done something with it much sooner. It was an AngularJS project, and the template was set up by someone who was, like most of us, new to Angular. Unfortunately he left us a legacy of some bad decisions. One was an odd directory structure (which I couldn’t fault him for, since there are half a dozen options and nobody can agree on which one to use) but the worst one was that he left us with the Deferred Antipattern and everyone new just copied it without a second thought.
And it’s not like I wasn’t making mistakes too. Like a lot of people with a JQuery 1.8-ish background, I came with expectations, not all of which were correct. The presentation was supposed to be about some anti-patterns and the things I’d learned, but in the process of prepping the examples, I discovered that two of my assumptions were incorrect. With that new information I realized that a few of my unit tests were failing silently because of them.
JQuery Promises Match No Spec
Where AngularJS and JQuery Agree
As in almost all Promises implementations, you can register callbacks for success, for failure, for both, and for errors. You can compose promises, and you can manufacture your own promise to do asynchronous work (maybe processing or collating several REST responses). You can register a late callback, that is, a callback for a Promise that has already been fulfilled, and you can be assured that the handlers will be called in the order they were registered.
Where the Similarities End
In JQuery, a callback on a resolved promise is fired synchronously. This means that on a late binding (eg, in the case of caching) any code after the line registering the callback will run after the handler has already been called. This can create some subtle bugs, and in the case of composing promises can even result in race conditions that only show up under server load, or on a slower or faster machine (or network connection).
In Angular, all callbacks are asynchronous. Even with a resolved promise, the callback will fire in the same order it would if you had to make a network request. That whole class of bugs and the attendant unit tests evaporates (Note! this comes at a price of more boilerplate in the rest of your unit tests).
Angular promises can chain in a way that JQuery won’t, but this can confuse people who don’t expect it. If the result of a network request conditionally requires a second network request to honor, you can resolve a Promise with another Promise, and the Angular internals will handle the callbacks.
The Gotcha here is that the Angular Promises API looks like a fluent interface, but in fact it is not. Every time you call a function on a Promise, you get a new Promise. While this allows for some pretty cool use patterns, and prevents you clobbering some things, it does have implications around what happens when two callbacks are registered.
What Doesn’t Work?
In Angular, one the best use of Promises is in Services that make REST calls, and wiring them up to resolve() in a view. Within resolve, these work pretty well, but they won’t work in a watch, or in an ng-bind variable. Instead you’ll have to register your own callback and assign the answer to a scope variable if you want the page to automatically update.
My source material came predominantly from these links, and a lot of time spent experimenting in JSFiddle:
All of my examples are on my JSFiddle: http://jsfiddle.net/user/jdmarshall/fiddles/