Callback heavy languages can be very challenging to think about. They require keeping quite a few disparate pieces in your mental stack all at once.<p>Promises make this both better and worse, in my experience. In the easy cases, all you have to do is follow the promises syntax, and you can treat it as linear code.<p>The problem arises when writing non happy path code, you not only have to think about the promises syntax, but about the abstraction it's creating over callbacks. Otherwise, you begin to miss corner cases, create incorrect promises, and in general write code which misbehaves (often silently).<p>Promises are powerful abstractions, but like all abstractions, they have their leaks. Also like all other abstractions, there are places where you have to fully comprehend the mechanisms underneath them to use them properly.
The real confusion here is dynamic typing. It's weird that the parameter to .then() can return any value, but if it's a promise there's special behavior. And for no good reason either<p><pre><code> p.then(r => r + 5);
</code></pre>
Is just a bit of sugar for<p><pre><code> p.then(r => Promise.resolve(r + 5));
</code></pre>
And then the type signatures are easy to reason about. The dynamic typing also risks introducing hard to find bugs. Suppose you have an heterogeneous array `things` which might contain numbers and promises which will resolve to numbers.<p><pre><code> p.then(r => things[r])
.then(thing => selectedThings.push(thing));
</code></pre>
You might intend `selectedThings` to also contain either numbers or promises that resolve to numbers, ordered deterministically as a function of `p`, but instead it will contain only numbers and its order is nondeterministic (it depends on `p` and all of the promise members of `things` that `p` ever signals).
> Rookie mistake #3: forgetting to add .catch()<p>I disagree with this advice. I usually refer to this as a "Pokémon Catch" (Gotta' Catch 'Em All!). Never catch a promise that you are un-able to handle. It's better to use a Promise library that supports debugging possibly unhandled exceptions (e.g., Bluebird).<p>> Rookie mistake #4: using "deferred"<p>This one is interesting because it's actually used in the Q library documentation for a way to `denodeify` or `promisify` a function. You definitely shouldn't do this if it's already a promise, but outside of that, it's more of a gray area. I tend to recommend using libraries to convert callback functions to promises.
I would argue that `Promise.all` is not the equivalent of `forEach` because Promise.all will execute everything immediately, maybe all will fail, maybe 1 will fail, eventually you'll have your then or your catch called but 100 of your actions will have an attempt to be executed.<p>Compare that to forEach, if one fails and you throw the rest don't get executed.<p>I suppose if whatever you're doing for each thing is async then they are equivalent equivalent but in general forEach has different sementics than Promise.all
Great article; a few things I would add. I use bluebird for Promises, which is just the most fantastic Promises lib ever conceived, no joke; if you haven't used it try it. So some of these may be Bluebird-specific:<p>1. Don't wrap callback functions manually with `new Promise(function(resolve, reject) {...})`, just use `Promise.promisify(...)`. For one-off functions, try `Promise.fromNode(function(cb) { fs.readFile('..', cb); });`.<p>2. This pattern:<p><pre><code> getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id);
}).then(function (userAccount) {
// I got a user account!
});
</code></pre>
Could be:<p><pre><code> getUserByName('nolan')
.get('id')
.then(getUserAccountById)
.then(function (userAccount) {
// I got a user account!
});
</code></pre>
3. I too used Promise.resolve().then(...) to start a lot of route handlers. Try `Promise.try(function() {...})`, which is equivalent but reduces the temptation to just stick a synchronous value in the `Promise.resolve()` just because you can.<p>4. `Promise#nodeify()` is <i>super</i> useful for creating functions that return promises or use callbacks depending on how they're called. For example:<p><pre><code> function getUserName(id, cb) {
return db.getUserAsync(id).get('name')
.nodeify(cb);
}
</code></pre>
Is the same as:<p><pre><code> function getUserName(id, cb) {
var promise = db.getUserAsync(id).get('name');
if (!cb) return promise;
promise.then(function(result) { cb(null, result);})
.catch(function(e) { cb(e); });
}
</code></pre>
This is great if you want to convert a few functions you use to promises, but they're called elsewhere and expect a callback style.<p>I'm sure there are many more. This is my bible: <a href="https://github.com/petkaantonov/bluebird/blob/master/API.md" rel="nofollow">https://github.com/petkaantonov/bluebird/blob/master/API.md</a><p>In short; use Promises! They are the answer for callback hell in Node. Async/await may fix more problems in the future but if you want Node/Browser compatibility and to get started right now, Promises are the best way to go.
The messes that callback/promise-heavy JavaScript make are a good example of why we need syntactic abstraction in the language. With a proper macro system, this whole async mess could be abstracted away and we could write nice linear code.
its interesting that when people talk about the newest feature or framework in javascript, they tend to forget about javascript's core functionality. It's always necessary to look at things like promises with javascript's core functionality in mind. Take #3 for example, which is really just a function of the core way javascript executes, taking promises aside!<p>You would not do something like this would you in a normal day at the office?
function myFunction(doSomething()){<p>};<p>so why would you do this.<p>Promise.then(doSomething());<p>doSomething() gets immediately invoked when the code is evaluated. It has nothing to do with Promises.<p>Don't forget the fundamentals!
What's so bad about using deferred? In one case I call the resolve or reject parameters and in the other I call a resolve or reject properties on the deferred object. Not much of a difference to me.<p>I learned about deferred a few years ago and kinda stuck with it. It's all over my code base and he didn't really justify why I should go about changing it. The only thing I can reason I can think of is using his recommendation follows the ES6 spec which doesn't matter to me that much for now.
Re "Advanced Mistake #4", do Javascript programmers not know about tuples? Does nobody write functions with signatures like<p><pre><code> pair(pa: Promise<A>, pb: (a: A) => Promise<B>): Promise<{fst: A, snd: B}> {
return pa.then(function (a) {
return pb(a).map(function (b) {
return {fst: a, snd: b}
})
})
}
</code></pre>
It's a super reusable little chunk of code.
If anyone's confused by the 4 "puzzles," I whipped up a JSBin to demonstrate: <a href="http://jsbin.com/tuqukakawo/1/edit?js,console,output" rel="nofollow">http://jsbin.com/tuqukakawo/1/edit?js,console,output</a>
I made the transition from callbacks to generators/iterators recently, and I'm really enjoying the yield/next foo. Promises just never really spoke to me. Not certain why I always felt reticent to use them.
Gosh, adding a `.catch(console.log.bind(console))` is just insane.<p>If you have a library that will return non operational error, just remove it from your project. If your code throws when it is not expected to, fix it.<p>This is like saying put a `try/catch(ignore)` everywhere. Seriously.
Very nice article, will keep that in mind when trying to help others with promises. Also helped me to re-understand some things :)<p>One thing though, Advanced mistake #4, is in my opinion good answer, the Q library however gives a (afaik) non-a+-standard way of doing that which I like:<p>from:<p><pre><code> getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id);
}).then(function (userAccount) {
// dangit, I need the "user" object too!
});
</code></pre>
to:<p><pre><code> getUserByName('nolan').then(function (user) {
return [user, getUserAccountById(user.id)];
}).spread(function (user, userAccount) {
// I do have the user object here
});</code></pre>
The most comprehensive and foolproof way is to grab the spec, read the algorithm and fiddle around a day. Sadly this is the only way of fully understand promises, promises already put a lot of cognitive load on your brain when you're using them, so having any other abstractions of your own (for remembering how promises work) is bad idea. IMO you're better off investing a large continuous block of time for understanding promises rather than reading some article here and there.
I am stumbling over this:<p>> Just remember: any code that might throw synchronously is a good candidate for a nearly-impossible-to-debug swallowed error somewhere down the line.<p>Why would a synchronously thrown error be swallowed, and why would I not just `try { } catch` here?
Puzzle number 3 doesn't have a complete explanation. DoSomethingElse() can return a function, which is then evaluated with the result of the first promise as an argument.
As soon as you can reasonably introduce an ES6/7 transpiler into your toolchain you should start using ES7's async/await or equivalently ES6's generators + a coroutine library function like Bluebird.coroutine, Q.async, co, or Task.js.<p>It solves basically all of the problems mentioned in this article.
hahahahaha this one cracked me up
"Writing code without a stack is a lot like driving a car without a brake pedal: you don't realize how badly you need it, until you reach for it and it's not there."