Consistency Problems With APIs In JavaScript Promise Libraries

Promises are a powerful way to handle asynchronous code in JavaScript. They give us a lot of opportunity to clean up code, and to write code that can register work to be done when the promise is resolved, whether or not the promise has already been resolved. The emergence of the various Promises spec has been great in the last few years, and we are moving toward a more consistent behavior and API set for the core of what promises do.

However… We’re not there yet, and it’s frustrating me nearly every day.

I work in several different projects and runtime environments, and I have to deal with a lot of different promise libraries because of this. The result: a constant learning and re-learning battle, because every Promise library does something slightly different than the others, even when the libraries in question are written to the same Promise spec.

Let’s look at how you create and resolve a promise in q.js, RSVP.js, WinJS, and jQuery. But before I look at them, I want to let every author and contributor to each of these libraries know that I really do appreciate the work they’ve put in. I use these libraries (or have used them in the past, in the case of WinJS) with great effectiveness in my applications. My code is far better for having put the time and effort in to each of these libraries. The inconsistencies, though, are killing me.

q.js: Deferred Objects And Promises

I’ll start with q.js because I’m using it in one of my projects and it’s on my mind at the moment.

// q.js
var q = require("q");

// create a deferred object, not a promise
var deferred = q.defer();

// now you can get the promise
ver promise = deferred.promise;

// wait for resolution
promise.then(function(){
  // ... do work here
});

// when you want to resolve it, though, you resolve the deferred object
// not the promise
deferred.resolve();

Q uses the idea of a “deferred” object to handle the real processing and the promise is only the public API of the promise. You should note that q does not allow you to call .then() or other “promise” methods on the deferred object, though. You can only call those on the promise itself.

jQuery: Deferred Objects And Promises

Q is quite similar to jQuery’s idea of a promise. I honestly don’t know if there is a relationship / influence between q and jQuery, but there certainly looks to be on the surface. The same code in jQuery, though, is just different enough to frustrate you endlessly.

// jquery
var $ = require("jquery");

// create a deferred object, not a promise
var deferred = $.Deferred();

// now you can get the promise
ver promise = deferred.promise();

// wait for resolution
promise.then(function(){
  // ... do work here
});

// when you want to resolve it, though, you resolve the deferred object
// not the promise
deferred.resolve();

The major difference here, is that q does q.defer() and deferred.promise; while jQuery does $.Deferred() and deferred.promise(). jQuery Deferred objects also let you call .then() and other “promise” functions on the deferred object itself, unlike q.

These might not seem like much in differences off-hand, but when you are switching between NodeJS and Browser based JavaScript several times throughout a day, or hour, the difference will frustrate you to no end while you try to remember which syntax goes where. I know it drives me crazy, at least.

RSVP: Just Promises

Next up is RSVP. I’m also using this in a current project. It was brought in to my code through the use of another service in this project. I generally like RSVP as much as I like other Promise libraries – that is to say, I sing the praises of promises quite regularly and RSVP is great.

RSVP has yet another API for creating an manipulating promises and it is dramtically different than q or jQuery in how you get a promise up and running.

// rsvp.js

// you're not allowed to promise.resolve()
// there is no deferred object to manipulate
// you have to work inside of the promise callback
var promise = new RSVP.Promise(function(resolve, reject){
  resolve();
});

promise.then(function(){
  // ... do work here
});

If you’ve never seen RSVP (or WinJS – coming up next) this may be confusing. You are not allowed access to a .resolve() method on an object instance. Instead, you must provide a callback function to the Promise constructor function. This callback receives a resolve and reject parameter, which are themselves functions. The resolve method is the equivalent of deferred.resolve() in the previous q and jQuery examples.

Great. Yet another odd syntax that makes me do things in a strange place this time. I might see the point of this, in that you don’t have to worry about where the “public” API is. The only way to resolve the promise is within the callback function that the promise ran when it was created. But this is completely different than q or jQuery. It took me a bit to realize that I was not allowed to have a deferred object to resolve at a later point. I must put all my code to determine when to resolve or reject the promise, within the promise callback – or at least call out to a method that can determin this for me, to keep the code clean.

WinJS: Just Promises

RSVP isn’t alone, though. As I just mentioned WinJS (the Windows 8 JavaScript API for WinRT/Microsoft Surface applications) uses the same style of promise creation and resolution.

// WinJS

// like RSVP, you're not allowed to promise.resolve()
// there is no deferred object to manipulate
// you have to work inside of the promise callback
var promise = new WinJS.Promise(function(resolve, reject){
  resolve();
});

promise.then(function(){
  // ... do work here
});

Other than the WinJS vs RSVP parent namespaces, WinJS and RSVP’s APIs are pretty much interchangeable. I think there are some slight difference in other areas of the API, though, such as which methods WinJS provides for wrapping code in a promise, or providing progress updates, etc. I’m not sure if these difference fall in to our out of a given Promise spec, though.

Please Clean This Up

I’m begging the JavaScript community at this point. Please, for the love of developer sanity, continue to make progress on standardizing Promises. Please PLEASE please! Make sure you include the constructor functions and whether or not a Deferred object is part of the solution, and how to resolve and reject the promise.

Yes, I get that there are different needs in different situations. Some libs will have extra things. That’s awesome. But for the parts that are supposed to be the same, please make them the same – all the way around, from constructor function to method signatures and expected behaviors. Frankly it doesn’t matter to me all that much, which spec wins. Just make it consistent across all the promises libraries. There are far too many inconsistencies in promises, at this point.

 

 

Need To Learn More About Promises?

Promises are not “the promised land”, but they are a powerful tool and one that you should have in your belt. In spite of my complaints about the inconsistent APIs, I believe they are a necessary and important part of good JavaScript development. Well, maybe not “in spite of” – it’s more that “because promises are so important”, I believe this complaint on API needs to be heard and something should be done. But I’m splitting hairs and talking about things that a lot of people are even ready to hear, yet.

If you’re still trying to wrap your head around promises, trying to understand how they actually work and what they really do, you’re not alone. Promises are relatively new, and relatively misunderstood in the world of JavaScript programming. But I’ve got something that can help. I’ve produced a screencast that shows you how promises really work, behind the scenes, by walking you through the process of building a very basic promise library, from the ground up.

If you’re looking to wrap your head around these powerful objects, check out WatchMeCode Episode 13: Promises From The Ground Up.

 


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Async, Javascript, JQuery, NodeJS, Promises, Qjs, RSVPjs, User Experience, WinJS. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://joelcox.nl/ Joël Cox

    Promises are currently standardized (http://dom.spec.whatwg.org/#promises) and a new spec is around the corner (https://github.com/domenic/promises-unwrapping/blob/master/README.md).

    Can’t wait to see this implemented in the browser.

    • elijahmanor

      To piggy back off your comment it’s currently implemented in Chrome Canary/Dev/Beta under a experimental flag http://www.chromestatus.com/features/5681726336532480

      • http://joelcox.nl/ Joël Cox

        Cool, didn’t know this was already implemented. Thanks Elijah.

  • http://domenicdenicola.com/ Domenic Denicola

    We’ve got this.

    new Promise((resolve, reject) => { ... }) is where everyone’s heading (except jQuery, of course, whose “promises” don’t even deserve the name). Q already has Q.promise((resolve, reject) => { ... }), and we’ll be implementing a real promise constructor soon. And as Joël notes, the standard promises will be using that same syntax.

    BTW, the reason we didn’t standardize on deferreds (and the reason RSVP evolved to use the constructor as well, via guidance from myself and others) is because introducing a second conceptual entity, the “deferred,” is an unnecessary pedagogical burden.

    Historically, I believe credit for originating this pattern belongs with WinJS. Luke Hoban (Chakra team) made Mark Miller (TC39 promises/concurrency guy) aware of it. He added it to the concurrency strawman as Q.promise, from which it made its way into Q. Then, when RSVP was starting up, Yehuda and I mutually agreed that it was the right pattern to use for a greenfield library. Since then we’ve been working to nail down the details in Promises/A+, which in turn fueled the effort of the new ES6 promises spec.

    • http://www.joezimjs.com Joe Zimmerman

      While I found the idea of introducing an additional concept (deferred) a bit odd at first, I prefer it greatly to having to use callbacks and nesting for another thing. I also like the idea that the deferred can be stored somewhere else to be used within a different method or something.

      Also, I think everyone getting down on jQuery’s promises implementation is over-exaggerated. It works perfectly for 95% of my use cases. I know it doesn’t implement the standard fully, but if we don’t use everything specified in the standard, then why do we care?

      • Esailija

        There is no reason for you to use promises in those 95% situations – what you are doing is simply using event emitters. Your view of deferreds also confirms this.

        jQuery isn’t just not implementing the standard fully, it lacks the fundamentals of what makes a promise a promise and not just a glorified event emitter.

        • http://www.joezimjs.com Joe Zimmerman

          Umm… no. The only real place where Promises fail in jQuery is bubbling up exceptions as failed promises instead just allowing the exception to be thrown. You can’t tell me that just because I don’t need to worry about catching those errors 95% of the time that I can just use event emitters. This is utterly false.

          As for using deferreds, I don’t see how that has to do with anything. I like this:


          var dfd = $.Deferred();
          var promise = dfd.promise();

          setTimeout(function() {
          dfd.resolve();
          }, 1000);

          Instead of this:


          var promise = new Promise(function(resolve){
          setTimeout(function() {
          resolve();
          }, 1000);
          });

          One of the benefits of Promises helping a bit with callback hell, but that second implementation requires callbacks in order to work. Also, the entire asynchronous operation needs to be nested inside the Promise’s callback which is inflexible.

  • http://madhatted.com/ Matthew Beale

    fwiw, RSVP supports the Q defer syntax: https://github.com/tildeio/rsvp.js/blob/master/test/tests/extension_test.js#L44 but in light of Domenic’s comments I understand why this is not mentioned in the readme.

  • medikoo

    Firstly, jQuery is not considered as compliant promise library, it has many things wrong, and you shouldn’t be surprised to see it as different.

    Q and RSVP stand on same standard (Promise/A+) and you should be able to use promise objects that come out of them interactively. I don’t know about WinJS but it might be same case.

    Still, many things will differ among promise implementation, every author has some different ideas. What’s important is that their `then` implementation works same and that they follow Promise/A+ in it’s core concepts, but you shouldn’t complain in how they differ in custom stuff, it’s normal, it’ll never change and it’s ok.

  • Chief

    Add AngularJS’s implementation: $q, which is a subset of Q.js