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.
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.
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).
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.
In my experience, whenever you having trouble with callbacks/promises - it is time to refractor your code. Essentially, this JavaScript nature force you to write code with more code blocks, which is in the end very beneficial to testing.
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.then(r => r + 5);
Is just a bit of sugar for
p.then(r => Promise.resolve(r + 5));
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.
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).
This is exactly right. If the spec defined an API with simple semantics half these problems wouldn't exist. As it is `then` is overloaded to perform numerous distinct operations. This means it can't report any useful errors, because any value passed to `then` is valid. There was even discussion about this when the spec was being written, but it was decided the current semantics are more in keeping with Javascript. Unfortunately the spec authors were correct.
I don't agree with your first remark: what do you expect to be returned when you return a value not wrapped in a promise?
The ambiguity is actually the auto unwrapping of promises for subsequent then calls but I've yet to see a case where it's a problem (it's actually what makes the API clean looking when used).
For your last snippet, I don't see an array so I don't know what ordering you want to preserve (p always returns the same value so r will always be the same), if it's actually inside a loop, you should use Promise.all or Promise.map.
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).
> Rookie mistake #4: using "deferred"
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.
The problem with "var d = defer(); ...; return d.promise;" vs "return new Promise((resolve, reject) => {...});" is that the former does not protect you from synchronous throws. In the latter (at least with every promise library and native promise implementation I've seen) if the code in '...' throws, it rejects the promise with the thrown error. In the former, it throws synchronously, which especially in node is almost never what you want.
Besides, if you need to promisify a node-like function you should just use Bluebird's promisification features; no point in doing it yourself. The fact that Bluebird is also the most debuggable and performant Promise implementation (including native) is just icing on the cake.
Yes, adding `catch` is mostly an anti-pattern today, most promise libraries as well as native promises on most platforms will detect unhandled rejections for you if you know the hooks. Bluebird is particularly good at this, but Q, When, RSVP and native promises all do this.
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.
Compare that to forEach, if one fails and you throw the rest don't get executed.
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
You're right; I'm playing a bit fast and loose with the definition of "equivalent." However, I think it's what most people want when they reach for the forEach().
In that "Promise protips" gist (https://gist.github.com/nolanlawson/6ce81186421d2fa109a4), I have an example of something closer to what you describe: the sequentialize() function will execute one promise after the other, and fail as soon as a single one fails.
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:
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); });`.
2. This pattern:
getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id);
}).then(function (userAccount) {
// I got a user account!
});
Could be:
getUserByName('nolan')
.get('id')
.then(getUserAccountById)
.then(function (userAccount) {
// I got a user account!
});
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.
4. `Promise#nodeify()` is super useful for creating functions that return promises or use callbacks depending on how they're called. For example:
function getUserName(id, cb) {
return db.getUserAsync(id).get('name')
.nodeify(cb);
}
Is the same as:
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); });
}
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.
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.
Overall, totally agree, just wanted to mention two things.
2) I've always found passing callbacks by name to be very difficult to read/follow, but this is a case where I think ES6 arrow functions will help readability a lot.
getUserByName('nolan')
.then(user => getUserAccountById(user.id))
.then(userAccount => // I got a user account!)
4) Your example isn't quite equivalent, you should be using the 2-param version of .then(), not .then().catch(). In your current code, if your cb threw an exception, it would call it a second time with the error from the first time.
Bluebird is awesome. :) I especially love promisifyAll() and promisify().
The only reason I didn't include something like that in the post is that I didn't want to overwhelm newbies. I think it's already confusing enough what the difference is between q/when/RSVP/bluebird/jQuery promises/Angular promises, etc... And honestly, more and more I just use vanilla promises, or a tiny polyfill like Lie.
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.
In Scheme, for example, a solution would be likely to involve delimited reified continuations and not need much in the way of macros. It's not obvious to me that the problem can be solved accurately with just syntactic macros. Would the solution involve transforming code into CPS?
Yeah, i cant believe async support is not a required check box for programming languages nowadays. Almost all the code I write is asynchronous and it is a pita to always wrestle around different patterns exposed by various libraries, and write bloat for things that are essentially sequential just that they happen to involve a lot of io latency. No, instead we get syntax sugar for writing shorter get/set-properties.
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!
You would not do something like this would you in a normal day at the office?
function myFunction(doSomething()){
};
so why would you do this.
Promise.then(doSomething());
doSomething() gets immediately invoked when the code is evaluated. It has nothing to do with Promises.
This article has a lot of ambiguity which I think adds to the confusion.
For example I was looking at that question(3) thinking, does doSomethingElse() return a function, another promise, nothing?
Part of the problem of having a very forgiving, lose language and API is that it leads to hidden, subtle complications, highlighted by "Advanced mistake #5: promises fall through". Here the then() method accepts anything as a return type, but will only chain a function that returns a promise, not a promise it's self. This isn't present in the MDN documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...).
And yet, I had to give a presentation at my last contract with basically the same information in this blog post because half the team was getting it wrong and the other half was tired of cleaning up.
Not everybody knows their stuff (especially junior developers). In front end especially I've found people who can run CSS circles around me but can't, for instance, filter data returned from the server on more than one criteria without creating spaghetti in the process.
Most places you don't get to pick who you'll work with. Even when you sit in on the interview loop, shit happens. Railing against it (and I have, at length) has never done me a lick of good, and trying to maintain the fantasy that it would has gotten me nothing but trouble.
At some point you just have to identify what things people are good at and steer them away from working on things they suck at. And keep your resume up to date.
Understanding functions in JavaScript is not enough to be able to complete the quiz. You also need to understand the somewhat surprising semantics of the Promises .then() method.
Yes, I'm not anymore a heavy user of fibers and I could answer correctly all these questions. Is pretty much vanilla js with some little knowledge about promises.
Anyway I would suggest anyone to take a look into fibers, you can write sync non-blocking code which is a delight to work with. No need of callbacks and promises.
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.
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.
The main thing in my mind is that it means your code could have cases where it throws synchronously, and cases where the promise rejects with an error.
The main issue I've found is with the way that deferreds handle then failures and how "then" works.
According to the Promises/A+ spec, then should always return a new promise. With the jQuery deferred.then() that's not the case.
In practice this means that it's harder to recover from failures as you're processing your results because your all your onRejected callbacks are attached to the same deferred and so will all fire one after another.
If you're just dealing with one asynchronous event, for example an AJAX call, that might not be a problem but it can cause problems debugging more complex code if you're not very careful.
E.g. (Get request) -then-> (onGetSuccessProcessor, onGetReject) -then-> (onProcessSuccess, onProcessingReject)
If the get request fails the onProcessingReject will also execute even though we haven't run the onGetSuccessProcessor callback.
In Promises/A+ code you can return a new resolved promise from your onRejected callback and the next "then" will fire it's onResolve callback instead of onRejected.
Hope that helps.
(it's a lot easier to draw as a petri net than to explain in a comment on HN :)
It's just mountains of unnecessary extra code that is error prone because you have to manually wire it. When you chain a promise or promisify a callback instead of using a deferred or the promise constructor (both are just as evil here), the wiring is done implicitly and there is a lot less code.
Edit: What I mean by "wiring" is that you need to make sure that in all successful code paths `deferred.resolve` is called and that in all erroneous code paths (not always a single try catch either) `deferred.reject` is called.
It is, but it only works for pairs and then you wind up with the relatively nonsensical 'fst' and 'snd' names.
My preference would be:
let user = await getUserByName('nolan');
let userAccount = await getUserAccountById(user.id);
// stuff with user and userAccount
But I have long since decided that I am never writing ES5 again, and since I therefore need Babel anyways I may as well turn 'stage: 1' on and use async/await. It's basically do-notation for promises.
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.
Also having promise libraries which stuck closer to the basic monadic interface without lots of overloading would help things like Typescript and Flow catch all of the errors.
Gosh, adding a `.catch(console.log.bind(console))` is just insane.
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.
This is like saying put a `try/catch(ignore)` everywhere. Seriously.
Yep, Chrome is pretty great about printing unhandled promise rejections to the console. However, I just tested Firefox and Safari, and neither of them will warn you in that case - not to mention older browsers like Android <4.4 and IE <=11, which don't even have native promises!
So if you're ever trying to debug promises in a non-Chrome browser, you may find my "insane" advice pretty helpful. :)
Yeah. In chrome if you enable "break on exceptions" it will also break on unhandled promise rejections. This will take care of all your debugging needs.
Very nice article, will keep that in mind when trying to help others with promises. Also helped me to re-understand some things :)
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:
from:
getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id);
}).then(function (userAccount) {
// dangit, I need the "user" object too!
});
to:
getUserByName('nolan').then(function (user) {
return [user, getUserAccountById(user.id)];
}).spread(function (user, userAccount) {
// I do have the user object here
});
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.
naivePromiseAPI() {
if (foo) {
throw new Error('this will get swallowed!');
}
return somePromise();
}
Instead you want this:
cleverPromiseAPI() {
return Promise.resolve().then(function () {
if (foo) {
throw new Error('this won't get swallowed!');
}
return somePromise();
});
}
Because if the client of the API does something like this:
$('.my-button').on('click', function () {
cleverPromiseAPI().catch(console.log.bind(console));
});
Then the client might expect the error to get console.logged, but actually it won't in the naive case. That's because the error was thrown synchronously and thus bubbled up to the click handler, instead of being caught inside of the promise chain.
[+] [-] falcolas|11 years ago|reply
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.
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).
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.
[+] [-] nolanl|11 years ago|reply
[+] [-] Bahamut|11 years ago|reply
[+] [-] hippich|11 years ago|reply
[+] [-] adrusi|11 years ago|reply
[+] [-] noelwelsh|11 years ago|reply
[+] [-] Guillaume86|11 years ago|reply
The ambiguity is actually the auto unwrapping of promises for subsequent then calls but I've yet to see a case where it's a problem (it's actually what makes the API clean looking when used).
For your last snippet, I don't see an array so I don't know what ordering you want to preserve (p always returns the same value so r will always be the same), if it's actually inside a loop, you should use Promise.all or Promise.map.
[+] [-] vkjv|11 years ago|reply
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).
> Rookie mistake #4: using "deferred"
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.
[+] [-] phs2501|11 years ago|reply
Besides, if you need to promisify a node-like function you should just use Bluebird's promisification features; no point in doing it yourself. The fact that Bluebird is also the most debuggable and performant Promise implementation (including native) is just icing on the cake.
[+] [-] inglor|11 years ago|reply
[+] [-] greggman|11 years ago|reply
Compare that to forEach, if one fails and you throw the rest don't get executed.
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
[+] [-] nolanl|11 years ago|reply
In that "Promise protips" gist (https://gist.github.com/nolanlawson/6ce81186421d2fa109a4), I have an example of something closer to what you describe: the sequentialize() function will execute one promise after the other, and fail as soon as a single one fails.
However, if you need to actually run multiple promises in parallel, but cancel them as soon as a single one fails, then you might want something more advanced than promises, like RactiveJS (https://github.com/ractivejs/ractive) or RxJS (https://github.com/Reactive-Extensions/RxJS).
[+] [-] MasterScrat|11 years ago|reply
[+] [-] STRML|11 years ago|reply
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); });`.
2. This pattern:
Could be: 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.4. `Promise#nodeify()` is super useful for creating functions that return promises or use callbacks depending on how they're called. For example:
Is the same as: 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.I'm sure there are many more. This is my bible: https://github.com/petkaantonov/bluebird/blob/master/API.md
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.
[+] [-] loganfsmyth|11 years ago|reply
2) I've always found passing callbacks by name to be very difficult to read/follow, but this is a case where I think ES6 arrow functions will help readability a lot.
4) Your example isn't quite equivalent, you should be using the 2-param version of .then(), not .then().catch(). In your current code, if your cb threw an exception, it would call it a second time with the error from the first time.[+] [-] nolanl|11 years ago|reply
The only reason I didn't include something like that in the post is that I didn't want to overwhelm newbies. I think it's already confusing enough what the difference is between q/when/RSVP/bluebird/jQuery promises/Angular promises, etc... And honestly, more and more I just use vanilla promises, or a tiny polyfill like Lie.
[+] [-] vkjv|11 years ago|reply
[+] [-] davexunit|11 years ago|reply
[+] [-] mbrock|11 years ago|reply
[+] [-] Too|11 years ago|reply
[+] [-] johnnymonster|11 years ago|reply
You would not do something like this would you in a normal day at the office? function myFunction(doSomething()){
};
so why would you do this.
Promise.then(doSomething());
doSomething() gets immediately invoked when the code is evaluated. It has nothing to do with Promises.
Don't forget the fundamentals!
[+] [-] robmcm|11 years ago|reply
For example I was looking at that question(3) thinking, does doSomethingElse() return a function, another promise, nothing?
Part of the problem of having a very forgiving, lose language and API is that it leads to hidden, subtle complications, highlighted by "Advanced mistake #5: promises fall through". Here the then() method accepts anything as a return type, but will only chain a function that returns a promise, not a promise it's self. This isn't present in the MDN documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...).
[+] [-] juliangregorian|11 years ago|reply
[+] [-] LordHumungous|11 years ago|reply
No... you know how functions work in javascript.
[+] [-] hinkley|11 years ago|reply
Not everybody knows their stuff (especially junior developers). In front end especially I've found people who can run CSS circles around me but can't, for instance, filter data returned from the server on more than one criteria without creating spaghetti in the process.
Most places you don't get to pick who you'll work with. Even when you sit in on the interview loop, shit happens. Railing against it (and I have, at length) has never done me a lick of good, and trying to maintain the fantasy that it would has gotten me nothing but trouble.
At some point you just have to identify what things people are good at and steer them away from working on things they suck at. And keep your resume up to date.
[+] [-] simonw|11 years ago|reply
[+] [-] ttty|11 years ago|reply
Anyway I would suggest anyone to take a look into fibers, you can write sync non-blocking code which is a delight to work with. No need of callbacks and promises.
[+] [-] xtrumanx|11 years ago|reply
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.
[+] [-] loganfsmyth|11 years ago|reply
One place this is mentioned is: https://github.com/petkaantonov/bluebird/wiki/Promise-anti-p...
[+] [-] Lio|11 years ago|reply
According to the Promises/A+ spec, then should always return a new promise. With the jQuery deferred.then() that's not the case.
In practice this means that it's harder to recover from failures as you're processing your results because your all your onRejected callbacks are attached to the same deferred and so will all fire one after another.
If you're just dealing with one asynchronous event, for example an AJAX call, that might not be a problem but it can cause problems debugging more complex code if you're not very careful.
E.g. (Get request) -then-> (onGetSuccessProcessor, onGetReject) -then-> (onProcessSuccess, onProcessingReject)
If the get request fails the onProcessingReject will also execute even though we haven't run the onGetSuccessProcessor callback.
In Promises/A+ code you can return a new resolved promise from your onRejected callback and the next "then" will fire it's onResolve callback instead of onRejected.
Hope that helps. (it's a lot easier to draw as a petri net than to explain in a comment on HN :)
[+] [-] esailija|11 years ago|reply
Edit: What I mean by "wiring" is that you need to make sure that in all successful code paths `deferred.resolve` is called and that in all erroneous code paths (not always a single try catch either) `deferred.reject` is called.
[+] [-] tel|11 years ago|reply
[+] [-] phs2501|11 years ago|reply
My preference would be:
But I have long since decided that I am never writing ES5 again, and since I therefore need Babel anyways I may as well turn 'stage: 1' on and use async/await. It's basically do-notation for promises.[+] [-] nolanl|11 years ago|reply
[+] [-] arxpoetica|11 years ago|reply
[+] [-] naturalethic|11 years ago|reply
[+] [-] embwbam|11 years ago|reply
[+] [-] tel|11 years ago|reply
[+] [-] giulianob|11 years ago|reply
[+] [-] tel|11 years ago|reply
[+] [-] neumino|11 years ago|reply
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.
This is like saying put a `try/catch(ignore)` everywhere. Seriously.
[+] [-] ben336|11 years ago|reply
The goal isn't to leave errors in, its to expose them when something changes to make them happen so that you can do the 2 things above.
[+] [-] nolanl|11 years ago|reply
So if you're ever trying to debug promises in a non-Chrome browser, you may find my "insane" advice pretty helpful. :)
[+] [-] Too|11 years ago|reply
[+] [-] mweibel|11 years ago|reply
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:
from:
to:[+] [-] unknown|11 years ago|reply
[deleted]
[+] [-] tumbling_stone|11 years ago|reply
[+] [-] unknown|11 years ago|reply
[deleted]
[+] [-] janfoeh|11 years ago|reply
> Just remember: any code that might throw synchronously is a good candidate for a nearly-impossible-to-debug swallowed error somewhere down the line.
Why would a synchronously thrown error be swallowed, and why would I not just `try { } catch` here?
[+] [-] nolanl|11 years ago|reply