We have a problem with promises
pouchdb.com
pouchdb.com
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.
Anyway, the big benefit of async/await, to me, is that you really DON'T have to understand promises. :) For instance, at some point we will probably add ES7 examples to the PouchDB API docs (e.g. here: http://pouchdb.com/api.html#create_document), and you can imagine that, from the user's perspective, the word "await" will just be part of the API (e.g. `var doc = await db.get('id')`), and beyond that, they won't have to learn anything about promises or async or callbacks or any of that.
function ensureFoo() {
let promise = Promise.resolve();
if (!foo) {
promise = getFoo();
}
return promise;
} var ensureFooEquivalent = getFoo(); 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. p.then(r => things[r])
.then(thing => selectedThings.push(thing));
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).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.
If .then() was just .flatMap(), you could expect an error if you didn't return a Promise (for example, reject with a TypeError).
If .then() was just .map(), you could return a value of any type (for example, string or Promise<number>) and get back a Promise for a value of that type (Promise<string> or Promise<Promise<number>>).
The distinction between map and flatMap makes sense for, say, lists, because you often do want to keep the non-flattened structure around. From promises, I can't think of a reason why that would be desired. You just want to represent the final result after all necessary waiting is completed. I suppose a library could provide stricter map and flatMap for people who deeply care about this.
Nothing that's a type error, it should throw a runtime exception just like all type errors in javascript, and it will show up in the debugger for the programmer to fix.
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).
I'm not sure I quite understand what you're referring to, but I think you mean the fact that you can chain then calls
Promise.resolve(5)
.then(x => Promise.resolve(x + 5))
.then(x => Promise.resolve(x + 5))
.then(x => {
// x == 15
});
This is totally sound behavior. The simplified signature of `.then()` is then :: Promise a -> (a -> Promise b) -> Promise b
This makes `.then()` the `bind` function for the Promise monad. As a refresher for people who don't think about monads much: (>>=) :: Monad m => m a -> (a -> m b) -> m b
While on this tangent, I'll note that `Promise.resolve()` is the `return` function for the promise monad: return :: Monad m => a -> m a
Promise.resolve :: a -> Promise a
For your last snippet, I don't see an array so I don't know what ordering you want to preserveIt's a contrived example demonstrating how switching based on whether the return value is a promise can make hard to detect bugs. It's hard to come up with a short example for this because strange values turning up where they don't belong is a property of large, dynamically typed systems. So yes, the example doesn't use all the best practices.
The then() signature can be expressed in typescript and I suppose in alternatives like flow (using the union type): https://github.com/borisyankov/DefinitelyTyped/blob/master/e... .
The "overload everything every which way and assume that people are really sensitive to the differences" strategy just begs misunderstanding.
Monadic bind as the core and basis of your API is fantastic. It is exactly enough and no more than you need to define effectful sequencing semantics and, if you like, you can build everything atop it. And, honestly, you do anyway. But right now the design obscures the simple basis technology in order to have heaps of overload magic.
Having to do `Promise.resolve(r + 5)` doesn't really solve that much in terms of typing, namely the fact you can return a rejected promise or throw an error and so on.
As someone who wrote a promise library or two in typed languages - I don't see it as solving it at all, although what's usually done is that `Promise.resolve` is aliased to `ok` or something similar to simplify the boilerplate.
throw :: Error -> Promise a
reject :: Promise a -- I guess, not sure of the semantics hereJust because you don't know the reason doesn't mean there isn't one. This is especially the case with a community-driven spec like PromiseA+.
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.
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.
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
https://gist.github.com/dcherman/4dfba0d72c008ee5b59d
It's a reduce function, but it could work as a forEach as well.
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).
Also on your timing diagrams it may be worth indicating that methods don't start executing at the exact same time due to the single thread:
doSomething().then(function () { doSomethingElse(); }).then(finalHandler);
doSomething
|-----------------|
doSomethingElse(undefined)
..................|------------------| finalHandler(undefined)
....................|------------------|It's my understanding that the finally handler can not execute before the body of doSomethingElse has run. (Although I am unsure of a pretty/clear way to add this to a diagram ;) )
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.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.
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.
That said, on a client-side app there are cases where I'd use native promises. On the server - not a chance.
Thank you.
I must say the most prominent feature of bluebird has always been "debuggability" (not perf). Long stack traces, unhandled rejection reporting and warnings for many of the mistakes you cover (coming in 3.0, some of them listed at http://imgur.com/a/t3xng) are just priceless, especially when working with people who are not promise ninjas.
That's exactly what I meant when I said it's better for your tools to warn you, rather than trying to read and understand some lengthy article. :)
BTW for PouchDB, we use bluebird in Node and lie in the browser, and bluebird has been truly indispensable for debugging. Looking forward to it getting even better!
If you want to contribute we'd love docs contributions as you clearly have good technical writing. Here are the 3.0 docs (currently under work) https://github.com/petkaantonov/bluebird/tree/3.0docs/docs/d... any help with pages that explain promises conceptually (anti patterns page etc) would be highly appreciated.
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. module.exports = function (arg, optionalCb) {
return promiseReturningFn(arg).nodeify(optionalCb);
};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!
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...).
No... you know how functions work in javascript.
Just to drive this home: I've read the spec a few times when evaluating whether to integrate the concept into code I've written, soon after the pattern was introduced, and then again a year or so later. (Side note: the result of both evaluations was a "no thanks".) But at this point, I've never used or authored a Promise, ever. And before moving past the intro that contains the quiz in question, it's been over a year before I last looked at them, much less read any up-to-date spec.
The quiz in the intro doesn't actually do a good job of exercising the details of `then`, because they're all trivial; there's only a single use of `then` in each example, rather than a chain of multiple ones (or a chain involving `then` and `catch` for a less useful example). You can get past the quiz in the intro by having only a general understanding of how `then` works. In fact (and I can only speak for myself here, but this is how it played out when I went through it), not having an extensive familiarity with Promises may help for the quiz, because that knowledge isn't crowding your thinking, and all you see is function calls. The author points out the third example from the quiz as particularly sticky, but it was the easiest gotcha to catch with my mostly Promise-free background, because the effect where overlooking parts that can lead you astray (because you expect idiomatic patterns in that place) is pretty totally nonexistent.
It's the examples further into the article that you need to really look into and consider how `then` works. For example, in rookie mistakes #2 and #5, understanding how `undefined` is handled required backtracking to the intro where the spec is linked, in order to delve into interaction with "synchronous" return values, and `undefined` in particular.
Lesson for potential interviewers: don't rely on the intro quiz in this article for your fitness function to weed out candidates, because it could mean you're optimizing for the people you don't want (like me), rather than those you do. Do make sure to focus on the examples further in that actually exercise an understanding beyond the superficial.
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.
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.
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.
One place this is mentioned is: https://github.com/petkaantonov/bluebird/wiki/Promise-anti-p...
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.
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 :)
In jQuery 3.0, their Promises will be fully Promises/A+ compliant as long as you're using `.then` ( `.done` and `.fail` are remaining non-compliant to remain non-breaking with sync XHR I believe )
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}
})
})
}
It's a super reusable little chunk of code.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.You can go much further than this. The following operation
ap<A, B>(pf: Promise<(a: A) => B>): (pa: Promise<A>) => Promise<B> {
return (pa: Promise<A>) =>
pf.then((f) => pa.then((a) => Promise.resolve(f(a))))
}
can be used to extend the previous idea as far as you like. function ap(pf) {
return (pa) =>
pf.then((f) => pa.then((a) => Promise.resolve(f(a))))
}
Still, if I wanted to go full-hog static-typed functional I doubt I'd be using straight Javascript, I'd probably try one of the various Haskell-alike to JS converters. Promises with async/await syntax seems like the most practical of the various solutions whilst still feeling like Javascript.(Besides, I prefer dynamic typing and I find points-free style in Haskell to be obfuscating rather than clarifying, so it seems unlikely we're going to have the same opinion of the best way to accomplish things here. :)
I'd love to use Haskell-to-JS but I don't think I could bet a product on it quite yet.
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.
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.
In any case, logging error gives you no information anyway. Just try running: `console.log(new Error("..."))`. So at the very least you should do `catch(function(e){console.log(e.stack);})`
So if you're ever trying to debug promises in a non-Chrome browser, you may find my "insane" advice pretty helpful. :)
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
});> 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?
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.I had a moment of stupidity and first somehow understood that to be a general suggestion for error handling.
It solves basically all of the problems mentioned in this article.
doSomething(function (err) {
doSomethingElse(function (err) {
doYetAnotherThing(function (err) {
throw new Error(
"I don't bubble up to the top of the stack, " +
"because there is no stack!");
});
});
});
BTW a great trick is to tick the little "Async" box in the Chrome Dev Tools, so you can get a full stacktrace showing exactly where you came from. It works for both callbacks and promises: http://www.html5rocks.com/en/tutorials/developertools/async-...Naive event-based code is somewhat like a goto, in that every time an event comes in, you get a new stack constructed for that event. While you then get a stack if you call functions from the initial handler, every event that comes in whacks the stack afresh. So it isn't "the same as" a goto, but it does hurt the stack. Instead of a stack frame that represents the whole of the program, you get a series of "island" stack frames.
In some sense, this is the most fundamental reason why event-based programming is so painful, and it's the problem that all these multitudinous programming "async" techniques are trying to get around. Promises, for instance, can be looked at as a partial way of having a sort of stack around a particular sequence of operations that may be interrupted in arbitrary locations by this event-induced stack discarding. In this sense they can also be viewed as an inner-platform problem; where the language has "try/catch", for loops, etc, promises end up trying to recreate all that in a second layer above the bottom layer, with all the corresponding complexity of embedding a language in the language.
If you then end up asking, "Well, what if we just didn't discard the stack in the first place?", you end up in Erlang/Go/Haskell/Scala land.