I promised Nolan a response on why I didnt particularly agree with this post when I merged it, figure may as well here.
I think all the caveats applied to async function (ensure its inside a try/catch, dont use for in) points to how complicated it can be, http://jakearchibald.com/2014/es7-async-functions/ had to be amended with notes because a bunch of people very familiar with this style of coding still get it wrong. I cant see myself explaining to a new programmer how this all works.
I would really like to see us return to nice obvious blocking functions, taking the example the simplest way of all seems to be ...
let db = new PouchDB('mydb');
let result = db.post({});
let doc = db.get(result.id);
console.log(doc);
Let workers (or whatever equivalent they come up with in node) to make sure we dont block a single event loop and live happily ever after.
Basically copy erlang, but without actually having to use erlang.
Came to say the same. The more I think on this problem, the more I'm inclined to say promises aren't solving async well enough, and the only meaningful change would be what dale proposes - blocking functions in threads. This is what Bob Nystrom also concludes in a pretty compelling blog post, [1].
Some side notes - silent promise rejection is handled in io.js with the process 'unhandledRejection' event [2]. The difficulty with promises is that they can attach rejection handlers at any time, and so it's impossible to determine whether a rejection will never be handled. That's why the 'await' operator doesn't just throw if it's not inside a try/catch, and it's also why io.js includes the 'rejectionHandled' event [3], in case it turns out 'unhandledRejection' was emitted prematurely. Oh lord.
Also, keep in mind that ES7 is not standardized, so if you use a transpiler to get these new features, you're building on a bed of sand.
I agree that a purely blocking style would be best, and maybe we can hope for that in ES8. :) But async functions are still much nicer than promises.
And in practice, I don't think it will be that hard to explain to newbie programmers. Most of them will not be writing code that actually produces promises, just the code that consumes it (because they're not writing ajax/database libraries). So the "await" basically becomes part of the library API from their perspective.
E.g. they type "await library.doSomething()", their editor tells them that the surrounding function needs to be async, so they add the word "async," and they're done. They don't have to learn a completely new system in order to work with this library.
As for the uncaught Promise rejection project, it is certainly nasty (and has burned me many times as I worked on Pouch), but I'm hoping this can be solved by better tools. E.g. in Chrome, they recently started logging uncaught promise rejections to the console.
As a fan of go, I was also a bit disappointed at all the gotchas and how easy it is to make it wrong: if
let result = await db.post({});
works, why can't I
docs.forEach((doc) => await db.post(doc))
? I still have to keep promises somewhere in my head and work around them.
But I saw this post [0] the other day where the author reuses csp and go stuff and introduces them to js to basically achieve asynchronous actions in a synchronous manner:
function dbPost(doc) {
var ch = chan();
db.post(doc).then(function (res) {
csp.putAsync(ch, res);
})
return ch;
}
docs.forEach((doc) => yield csp.takeAsync(dbPost(doc)))
or, for your code:
go(function* () {
let db = new PouchDB('mydb');
let result = yield take(dbPost({}));
let doc = yield take(dbGet({}));
console.log(doc);
})
There still is some boilerplate compared to a fully synchronous code (and it introduces the notion of channels, which do have an added value other than just asynchronously waiting for a result), but overall I see it much more capable at the task than what is described in OP's article.
I like that we're all starting to wake up from this callback nightmare in pretty much every language. It's funny to me that it took us going through a full blown callback orgy for us to realize that all we wanted to begin with was an abstraction of spawning threads and doing non-blocking i/o (makes sense, having to reason about locking is, often, unnecessary). I realize callbacks/lambdas have their place, but it was really starting to chaff.
This is just me thinking out loud, but the look-ahead...will it ever end? You say ES7 is bleeding edge, but that's contingent on the context. In terms of browser support/widespread adoption right now, ES6 is bleeding edge. How many years...decades is something like ES7 away from ever being implemented in the majority of browsers out there? How much more will the web development landscape have even changed by then?
It seems mostly like a syntactical difference to me - with coroutines you "yield" instead of "await." You can still wrap things nicely in try{} catch{} blocks.
But the readme for co does mention that it's a stepping stone towards async/await. I like that ES7 will allow us to write this style of function without including an additional library, but apart from that, is there a big gap in functionality between the different approaches?
> However, promisey code is still hard to read, because promises are basically a bolt-on replacement for language primitives like try, catch, and return
I find promises quite readable when combined with arrow functions. async/await certainly is nice, but the difference in readability doesn't seem that big to me.
My main point was that promises represent an entirely new coding style, which pantomimes the familiar try/catch/return, but isn't quite the same. So you have to learn two systems.
For instance, the following three blocks are not equivalent, but they're easy to mix up:
With synchronous code, it's obvious what order things happen in, because you just read top to bottom. But yeah, I do agree that arrow functions make it a bit nicer. :)
That's more a property of arrow functions than of promises, though, isn't it? We could expect any promise-equivalent to look OK with arrows, too. Callbacks are certainly more palatable in coffeescript, for example.
Well yes, arrow functions are the magic ingredient.
But the point is that with them you can express custom control flows trough custom functions instead of built-in syntax without much of a loss of readability.
So we don't necessarily have to wait for ES7. We already have alternatives.
Some people have also built async from generators (yield) + promises. So that's another ES6 alternative.
Seems like there are a lot of edge cases described in this article that could be confusing when using async/await. Is anyone else feeling ambivalent that async/await is actually a nicer syntax than just using promises?
I'm using channels (with the awesome js-csp lib) for managing server-side asynchronous code and so far it's really good. As a bonus I can use transducers with generators :)
I think all the caveats applied to async function (ensure its inside a try/catch, dont use for in) points to how complicated it can be, http://jakearchibald.com/2014/es7-async-functions/ had to be amended with notes because a bunch of people very familiar with this style of coding still get it wrong. I cant see myself explaining to a new programmer how this all works.
I would really like to see us return to nice obvious blocking functions, taking the example the simplest way of all seems to be ...
Let workers (or whatever equivalent they come up with in node) to make sure we dont block a single event loop and live happily ever after.Basically copy erlang, but without actually having to use erlang.