Promises/A+
promisesaplus.com
promisesaplus.com
https://github.com/promises-aplus/promises-spec
Including a changelog:
https://github.com/promises-aplus/promises-spec/blob/master/...
And the issues still open versus closed in the 1.1 timeframe are at
https://github.com/promises-aplus/promises-spec/issues?miles...
We were hoping to do a revision of the website's look at the time of the 1.1 release as well, mainly adding links to additional resources in the repository (like the implementations list, credits, etc.). A bit sad that we haven't moved fast enough to finish that before making the front page of Hacker News!
I am kind of figuring things out as I go along but if anyone here can point me to such guides, I would be most grateful.
http://www.slideshare.net/domenicdenicola/callbacks-promises...
http://www.slideshare.net/domenicdenicola/promises-promises with video at http://www.youtube.com/watch?v=MNxnHbyzhuo
http://www.slideshare.net/domenicdenicola/boom-promisesa-was... which is more inspirational and motivational than practical
Otherwise, in the Q readme we try for a "graduated tutorial" approach; would welcome any feedback you have to make it more friendly. We try to relegate the "list of functions" to the API reference at https://github.com/kriskowal/q/wiki/API-Reference
The source should be fairly annotated and is structured similar to Backbone.js, so it might be useful to take a look through and see where/how those functions are being used.
The author of Promises/A+ also wrote a nice blost post on his You're Missing the Point of Promises [3]
Promises are actually a part of a of a very well understood programming mechanism that involves Futures and Deferred as well[4]
[1] http://blog.jcoglan.com/2013/03/30/callbacks-are-imperative-...
[2] https://news.ycombinator.com/item?id=5466872
This is my problem - when I last formally studied Computer Science we never covered this. I think most, if not all, of the Promises libraries assume you have a background that covers this, which makes it harder for those of us who have to work out all this from scratch. Not that I am complaining - just an observation.
Probably also harder for people like me that are not working with anyone who actually knows Node.js or Promises. The upside is of course that knowledge gained the hard way often sticks better, even if the process is more frustrating.
Hmm, let me check, yup. I understand promises fully.
Anywho. They are not hard to learn. Watch a few videos. James Coglan talks are excellent, watch twice even.
Finally if you don't like a library, find another, see if the glove fits.
Lets start with the most basic element of a promise: the `then` method. It is so basic in fact, that the spec only covers on how this method should behave and nothing else. All the other functions that you see in the libraries (Q, whenjs, etc) just have a lot of combinator functions that's built on top of the `then` method. Take the `Q.all` for example, it functions the same `then` except it wait for all promises passed into to it to finish before continuing with its own `then`. So understanding how the `then` behaves is essential to understanding promises.
IMO, the advantage of using promise is how composable it is. To achieve desired effect, you can take a few existing promise combinators, compose them together with just object/array literals and function calls.
For example, the other day I needed a html form to submit with a load mask covering the form while waiting for the submission to complete. The problem is that this submission can take anywhere from 21ms to 1500ms to complete. In order to achieve good UX, I needed the load mask to be not just a flash in the extremely fast case, but also not taking more than necessary time to complete in the extremely slow case. So I did this...
Q.all([ asyncFormSubmitReturnPromise(), Q.delay(500) ]).then(function() { // hide mask & other cleanup })
For comparison, to achieve the same effect using asyncjs...
async.parallel([ function(callback){ asyncFormSubmit({ success: callback }) }, function(callback){ setTimeout(callback, 500) } ], function(err, results){ // hide mask and cleanup })
I want to mention another advantage with promises: it creates an uniformed interface that disregards whether the loading is synchronous or asynchronous. Say for example you're writing a select box widget that needs to populate the dropdown's content. Your widget can expose a method just called `load` and it takes a promise. Now whoever is calling your `load` method will have the total freedom to pass in a promise's already resolved or pending to be resolved. If you have the data at hand, it's just as easy to wrap it in a promise `Q.resolve(data)` and pass it in.
promisesFunc().done(function(){ // execute on completion }
q.when(promisesFunc).then( function(){ // do stuff } )
It's nice to be able to treat something that is inherently asynchronous like it is inherently asynchronous. Feels good man.jpg. And just wait til you start mixing in generators (well, eventually).I wrote an alternate implementation of promises in JS (lowercase "p") that uses the former approach: https://github.com/greim/await.js
You can retrieve the results of promises in parallel, serially, or via some combination of both. That's part of their power versus straight callbacks which enforce a serial approach.
Check out this link to q's documentation: https://github.com/kriskowal/q#combination
function parallelCallbacks(cbs, onAllDone){
var nLeft = cbs.length;
var results = []
var onOneDone = function(res){
results.push(res);
nLeft -= 1;
if(nLeft <= 0){ onAllDone(results) }
}
for(var i=0; i<cbs.length; i++){
cbs[i](onOneDone);
}
}
parallelPromises([
function(){ return promise(10) },
function(){ return promise(20) }
).then(function(results){ ... });
parallelCallbacks([
function(onDone){ onDone(10) },
function(onDone){ onDone(20) }
], function(results){
...
});I also think promises' use of return values vs passing a callback as an argument is conceptually cleaner and easier to compose.
That said, I still feel that both promises and explicit callbacks kind of suck in their own way. Callback code is usually much more verbose and has bad error handling while promises are bad at "jumpy" control flow. When I last worked in a big JS project it got bad enough that I eventually wrote my own control flow library on top of the base Dojo promises we were using so nowadays if I had to work with a big JS project again I would definitely try to use of those tools that add async control flow to the language and compile doen to regular continuations (looks nice like promises, can use loops, break and return like regular JS and raw callbacks don't need extra runtime support)
The hard problem that promises solve are
1. Its more similar to regular code versus explicit CPS (no pyramid of doom, use "return" to return values, can store promises in data structures or pass them around to other functions)
2. Error handling. Regular Javascript has try-catch blocks for exception handling but those don't work with async code. Promises let you use exceptions in async code without having to worry about error callbacks.
I tend to find that the biggest problem with promise libraries is writing code with lots of jumps and more advanced control flow. If you are using an explicit continuation-based approach then doing a jump (for a loop or for breaking out early) is just a matter of calling the appropriate continuation. On the other hand, promises naturally tend to prefer the sort of code where there is only a single return statement at the end (unless you abuse the error handling system but there are all sort of problems if you do that)
$.when()
That API isn't written into the Promises/A+ spec because it doesn't need to be. That spec is the minimal things necessary for multiple promise implementations to be interoperable.
My issue with promises is that I find myself working around the API/language when I actually want synchronous code. My app calls a JSON API and then writes the results to three tables (in sequence). In "normal", blocking code:
results = getResults(); writeTable1(results.foo); writeTable2(results.bar); writeTable3(results.baz);
Using Node and Q: db.load().then(()-> setupDB(db)).then(()-> collectData(db)).done()
In which setupDB() returns a chain of three promises and collectData, which loops through the results and attach a new promise to the chain in order to return a chain of `n` promises.Are these just growing pains of learning a new paradigm or am I using the wrong tool for the job?
Can someone point me to any projects that actually use promises?
db.load()
.then(setupDB)
.then(collectData);
That way it looks much more like your blocking code.You are currently basically throwing away the value you `resolve` with, where you should instead resolve with what would be the return value if it was a sync function so you can `.then` the function that takes the return value and returns a promise to be resolved with its return value.
// Sync
var foo = function () { return 'foo' };
var addBar = function (foo) { return foo + 'bar'; }
addBar(foo()) == 'foobar'
// Promise using Q
var foo = function () { return Q.resolve('foo') };
var addBar = function (foo) { return Q.resolve(foo + 'bar'); }
foo.then(addBar).done(function (result) {
result == 'foobar'
});Perhaps I should look into IcedCoffeeScript (http://maxtaco.github.io/coffee-script/) instead of plain CS.
This is not to say that it invalidates your library or that every promise library has to comply to A+ or that A+ is the only good way to design promises in JS.
This is idiomatic JS I guess, but it bugs me to bits. IMO the parameter should be a function or it should be null; anything else should throw an error.
That said, I somewhat agree (although in JavaScript it should be `undefined`, not `null`). If we were starting clean, instead of with tons of code in the wild that did `.then(null, onRejected)` or `.then(false, onRejected)`, we would have the resulting promise be rejected with a `TypeError` if `onFulfilled` was neither `undefined` nor a function.
Sending in undefined for a non-trailing argument takes effort.
I think the literal null is far more communicative of the developers intent.
But the general point is, JS APIs tend to fail silently on error by design and that's something that bugs me, literally. Bugs. Lots of them.
I agree regards returning a failure rather than throwing an exception, though.
Meanwhile, ES6 generators are coming to node v0.12, already in FF and soon to be in Chrome; that is a whole different game.