Why I Am Switching To Promises
Why I Am Switching To Promises
Why I Am Switching To Promises
I'm switching my node code from callbacks to promises. The reasons aren't
merely aesthetical, they're rather practical:
Throw-catch vs throw-crash
We're all human. We make mistakes, and then JavaScript throw s an error.
How do callbacks punish that mistake? They crash your process!
And guess what a user that hits an error does? Starts repeatedly refreshing
the page, thats what. The horror!
1 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
For more info see #5114 and #5149. To find out how promises can solve this,
see bluebird #51
I understand that its important to explicitly handle all errors. But I don't
believe its important to explicitly bubble them up the callback chain. If I
don't deal with the error here, thats because I can't deal with the error there
- I simply don't have enough context.
I guess I could do that and lose the callback stack when generating a new
Error() . Or since I'm already wrapping things, why not wrap the entire thing
with promises, rely on longStackSupport, and handle errors at my
discretion?
Containing Zalgo
Your promise library prevents you from releasing Zalgo. You can't release
Zalgo with promises. Its impossible for a promise to result with the release of
the Zalgo-beast. Promises are Zalgo-safe (see section 3.1).
2 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
20000
3 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
Parallel requests
20000
Parallel requests
4 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
Promises are not slow. At least, not anymore. Infact, bluebird generators are
almost as fast as regular callback code (they're also the fastest generators as
of now). And bluebird promises are definitely at least two times faster than
async.waterfall .
5 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
However, both bluebird and Q give you promise.nodeify . With it, you can
write a library with a dual API that can both take callbacks and return
promises:
6 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
Promises are objects that have a then method. Unlike node functions, which
take a single callback, the then method of a promise can take two callbacks:
a success callback and an error callback. When one of these two callbacks
returns a value or throws an exception, then must behave in a way that
enables stream-like chaining and simplified error handling. Lets explain that
behavior of then through examples:
Imagine that node's fs was wrapped to work in this manner. This is pretty
easy to do - bluebird already lets you do something like that with
promisify() . Then this code:
7 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
So far, this doesn't look that different from regular node callbacks - except
that you use a second callback for the error (which isn't necessarily better).
So when does it get better?
Its better because you can attach the callback later if you want. Remember,
fs.readFile(file) returns a promise now, so you can put that in a var, or
Yup, the second callback is optional. We're going to see why later.
Okay, that's still not much of an improvement. How about this then? You
8 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
Hey, this is beginning to look more and more like streams - they too can be
piped to multiple destinations. But unlike streams, you can attach more
callbacks and get the value even after the file reading operation completes.
What if I told you... that if you return something from inside a .then()
callback, then you'll get a promise for that thing on the outside?
Say you want to get a line from a file. Well, you can get a promise for that
line instead:
Thats pretty cool, although not terribly useful - we could just put both sync
operations in the first .then() callback and be done with it.
But guess what happens when you return a promise from within a .then
callback. You get a promise for a promise outside of .then() ? Nope, you just
get the same promise!
9 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
Now its easier to understand chaining: at the end of every function passed to
a .then() call, simply return a promise.
Mind = blown! Notice how I don't have to manually propagate errors. They
will automatically get passed with the returned promise.
What if we want to read, process, then upload, then also save locally?
10 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
No, these are not "conveniently chosen" functions. Promise code really is
that short in practice!
Thats all you need, really. The rest is just converting callback-taking
functions to promise-returning functions and using the stuff above to do
your control flow.
You can also return values in case of an error. So for example, to write a
11 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
You can also throw exceptions within both callbacks passed to .then . The
user of the returned promise can catch those errors by adding the second
.then handler
Finally, you can make sure your resources are released in all cases, even
when an error or exception happens:
12 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
Or you can do the same using .finally (from both Bluebird and Q):
The same promise is still returned, but only after cleanUp completes.
You already have async.waterfall and async.auto with .then and .spread
chaining:
13 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
What if you want to wait for the current item to download first (like
async.mapSeries and async.series )? Thats also pretty straightforward: just
wait for the current download to complete, then start the next download,
then extract the item name, and thats exactly what you say in the code:
The only thing that remains is mapLimit - which is a bit harder to write - but
still not that hard:
14 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
15 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
The promise version can do pretty much the same - just nest the rest of the
chain inside the first callback.
That means that early returns are just as easy as with callbacks, and
sometimes even easier (in case of errors)
16 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
Or how about stream pipelines that are safe from errors without attaching
error handlers to all of them?
The future?
In ES7, promises will become monadic (by getting flatMap and unit). Also,
we're going to get generic syntax sugar for monads. Then, it trully wont
matter what style you use - stream, promise or thunk - as long as it also
implements the monad functions. That is, except for callback-passing style -
it wont be able to join the party because it doesn't produce values.
I'm just kidding, of course. I don't know if thats going to happen. Either way,
promises are useful and practical and will remain useful and practical in the
future.
Tweet
submit
17 of 18 12/01/2014 14:45
Why I am switching to promises http://spion.github.io/posts/why-i-am-switching-to-promises.html?utm...
5 comments
Best
v0xCAB •
Dennis Leukhin •
Esailija •
Promise.cast(ids).bind([]).map(function (id) {
var mustComplete = Math.max(0, this.length - PARALLEL + 1);
return Promise.some(this, mustComplete).bind(this).then(function () {
var download = getItem(id);
this.push(download);
return download;
}).get("name");
}).then(function (names) {
//Use names
});
• •
bcs •
Owen Densmore •
As much as I wish ES6 was "just around the corner", alas getting the browsers to agree
(mainly Chrome & FF, the others are toys) is running into The Browsers, The Next
Generation Wars.
You see, FF/Moz is doing a great job of promoting two directions at one time: asm.js and
ESnext. Chrome is promoting Dart, tolerating JS, and JIT oriented. Look at the compatibility
tables and you'll see chrome ignoring ESnext.
So if you would like to use Promises (who wouldn't!), you better suck it up and choose
whatever library you prefer and get on with it. The browser simply won't help. Not for years.
• •
18 of 18 12/01/2014 14:45