ES7 async functions – a step in the wrong direction (2015)
spion.github.io
spion.github.io
A similar thing is how when you return a Promise from a 'then' callback, the resulting promise does not resolve to an inner promise but just to the value that was inside the inner promise. This completely breaks the monadic properties of Promise, but given that JS has no nice support for monads, it makes code simpler and easier.
IMO, but Promises/A+ spec designers made the right call here. Purists will disagree, but I bet the majority of the JS community hasn't even given this a thought (yet uses the feature all the time). I feel the same about async/await.
Also, anecdotally, at my company we're heavy heavy users of async/await ever since Babel added support for it, and we've yet to run into any of the problems the author refers to.
Been happy so far.
I wrote a ton of code last year to migrate data from one system to another... without async/await, Promises and node-style streams, it would have been really nasty, deeply nested code... as it was, the flows were fairly functional, really elegant, and worked extremely well. I will say that Bluebird is useful beyond what the Promises spec offers, and have gone back and forth between using it, replacing the native Promise where available and just using whatever promise shim or native is available.
I didn't like promises earlier on, as without some of the extras that async functions offer, I didn't consider them much better than callbacks. Now, I'm sold.
I don't think the problems I'm describing here are all that uncommon in server-side code. Continuation local storage (the equivalent of thread local storage) in particular seems to be an often requested feature by many users in node.
Typically, that is not the argument. Typically, the argument is that the "elegant abstraction" is just as readable, just in a different way. And further, the typical argument for elegant abstractions is that the “readable code” option is usually most readable for a specific, narrow problem to solve, and that it breaks down when you try to do more complicated things.
So if you buy the argument in favour of elegant abstractions, you are trying to make “simple things simple, and complicated things possible.” Whereas the argument in favour of special keywords and other special-purpose constructions is that they also make simple things simple, but do little to make complicated things possible.
The argument in favour of languages having these “readability” constructs is actually that they foster standardization, which makes them readable by virtue of the fact that everyone settles on doing them the same way. Which means in fact that they aren’t more readable in an isolated, objective sense. They’re actually more familiar.
That has value in its own right, of course. But I don’t think the author--or anyone else who isn’t thrilled by some of the TC-39 choices--is arguing that there is an elegance vs. readability dichotomy.
In actual fact, the argument is between elegance and familiarity by fiat.
This code:
async function renderChapters(urls) {
urls.map(getJSON).forEach(j => (await j).html));
}
is assuming that the call to await j is the await which corresponds to the top-level async. That's not how these things work! That call to await j is not in the same scope!I don't buy arguments where the misuses of a feature not working means the feature is somehow bad. Yes, it can seem confusing, but that's the nature of having more powerful constructs. When you mix asynchronous code and lambda expressions, you're going to have to pay attention to scope.
You could make a similar argument that Promises are insufficient, and you should really be using Observables, because there are lots of situations where the generality is even better.
On the other hand, there's no universal hammer, and no reason why you can't mix all approaches, choosing each where it works best.
One of "our own interpreters" (i.e not standard in JS), that I am very fond of, is js-csp [1]. It's a replica of Clojure(Script)'s core.async, or Go's channels/goroutines. It uses ES6 generator functions underneath.
James Long has written an excellent post about it [2]. I find it to be the less taxing on the mind solution for coordinating complex asynchronous processes in JS, especially when paired with transducers [3]. Furthermore, powerful Go/Clojure patterns can easily be re-used in JS.
In conjunction with CoffeeScript, the code becomes elegant and straightforward since there is no 'function*' ugliness required.
That's the least complex solution I have found to like the most. Performance and memory usage suffer a bit, but it's very straightforward to optimize if/when needed.
[1] https://github.com/ubolonton/js-csp
[2] http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-...
[3] http://jlongster.com/Transducers.js--A-JavaScript-Library-fo...
https://github.com/petkaantonov/bluebird/issues/1014
Its almost done [1], we just want to also support auto-disposable objects so that you don't even need to clean them up with defer.
[1]: https://github.com/petkaantonov/bluebird/commit/f944db753ff2...
Are programmable semicolons optional, and can you program automatically inserted semicolons?
It brings to mind my favorite C++ extension: Generalized Overloading for C++2000, that lets you override specific types of white space including space, tab, newline, and even the absence of white space! [1]
FORTH has programmable semicolons!
: ; POSTPONE EXIT REVEAL POSTPONE [ ; IMMEDIATE
There's an interesting comp.lang.forth discussion [2] where somebody asks a great question about defining semicolon in Forth, that's promptly answered by the great Elizabeth D. Rather herself, of FORTH Inc. [3] [4]![1] http://www.stroustrup.com/whitespace98.pdf
[2] https://groups.google.com/forum/#!topic/comp.lang.forth/79XR...
>Lets drop async/await and write our own interpreters.
Guess what kind of "interpreter" most people would need? The async/await kind. It doesn't solve everything and it does introduce more awkwardness[1] to the language but it's categorically a productivity and readability boost. Now to address the individual points raised:
1. Try/Catch
The argument here is against a core language feature and has nothing to do with async/await. Sure, guarded catch statements would be nice, but I contend that if you're relying on multiple levels of exception handling then you're using exceptions for flow control.
2. Higher order functions
JavaScript is a multi-paradigm language and as mentioned earlier that leads to awkwardness. You can only pull the language in so many directions before it becomes inconsistent. Some features will be geared towards functional paradigms and other will be imperative by nature. For example instead of `forEeach` you can use `for of` which allows you to yield inside the loop. In that sense async/await, for-of, and try/catch are all imperative control flow constructs. On the other hand callbacks, promises, and higher order functions are functional constructs. Mixing them together is not easy (nor should it be if you ask me).
3. "generality"
In this case you might not want to use such a high-level construct like async/await. Just like when you have to do caching or memoization you tend to drop down to do promises again. However, you can push this down into a library that exposes a promise and then continue using async/await in the rest of your application. In this specific case you want a monad-like abstraction, something that carries certain context across a sequence of computations. The generator implementation might be an overkill but the syntax looks nice.
[1]: This is a great write up on how adding different types of functions to the language makes it awkward to say the least: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
I don't see how this is a problem. Try/Catch are by definition flow control keywords. Also, when you get an exception you have to differentiate between how you recover from it or package it into something that can be logged or used by a higher level caller.
That said, I don't disagree with any of your other statements.
Because we never got the chance to explore the full power of generators, and we probably never will because of it. We will of course solve our other problems the usual old-fashioned way (e.g. DI for transactions, some other funky mechanisms for CLS, no automatic SQL query optimizations, no automatic GraphQL query optimizations).
You can't need what you aren't aware of.
> The argument here is against a core language feature and has nothing to do with async/await.
Not directly, no, but thanks to async/await a lot more errors will be flying up the call stack than ever before. This will greatly increase the chances that you catch something you didn't intend to catch. Unless you have filtered catch, which would improve that a little bit.
Let's "codify" patterns instead of having everybody roll his own implementation thus make codebases more compatible.
All criticisms made in the article are 100% valid, however the former principle trumps every other one.
I don't want having to import yet another micro-module just to write something that should be trivial to do in plain javascript.
As for try/catch, some languages support pattern matching, why not Javascript?
try {
(17).toString(42);
}
catch (ex if ex instanceof RangeError) {
console.log("caught");
}
catch (ex) {
console.log("handling something else");
}
The if's clause has standard standard conditional semantics, so you can use any expression there; you aren't limited to selecting on the exception's prototype: try {
throw new Error("whatever");
}
catch (ex if ex.message == "whatever") {
console.log("caught");
}For example if language has special syntax for numbers people tend to use number as type of account (or string for contact person). Then when software evolves and new information needs to be kept for account and new operations performed the shorthand syntax stands in the way. You can't easily change the type of account from number to complex object without refactoring all of the places in your code where account was used as number. (I'm ignoring languages with overridable operators to make the point).
People tend to obsessively stick to built in stuff (anti-pattern Primitive Obsession) because swapping for what they need now is excessive burden.
What I would very much like to see in languages development is that whenever the syntax gets introduced programmer should be able to use it on more complex things then the syntax was primarily designed for.
If you introduce syntax for operators don't make them work just for few built in types. If you introduce async/await don't make them just work on promises (especially not just built ins) but anything that is shaped like a promise. If you introduce generators make sure that they programmer can implement persistable version of them with the same syntax.
What I said was more general argument about what each new syntax should support at minimum.
What you are saying is like having operator overload, but not ability to define new custom operators, or not being able to define 3 or more arguments operators or default arguments for operators, or quoted arguments for operators. Or regretting that C# foreach works on every Enumerable but takes every item one after another (preventing from implementing collection that would have parallel behaviour with foreach)
It's good if you can do a lot with new syntax but that's nice to have and case by case. What I was talking about I consider general and pretty much mandatory.
Lots of people feel that if you look at the piece of code you should be immediately be able to tell what it's doing without any context. It's intuitively attractive goal but I think that most measures that achieve that also result in putting obstacles in software usual evolution making people do very contorted things when they need to add yet another feature with limited time.
Maybe my sarcasm detector is off but is this really being presented as a reasonable solution to the problem discussed in the article?
Edit:
Also after reading the article it strikes me that ES6 and ES7 features are going to create a huge gap in developer knowledge between those who fully understand ES7/8 and those who only understand ES5. Will we one day hear "oh beginners should start by learning ES5 and then do ES7?". Doesn't that sound a lot like c++?? Do we want that for javascript?
A lot of the things I love from Coffeescript got picked up in ES6/ES7, so it wasn't a big deal for me to transition. :-) (sad that Coffeescript will decline when it has more to offer..)
I had to work on an ES5 project recently and I was almost in tears because I couldn't use a computed property. (someObject[someValue]) I wound up writing a switch to assign the correct index, and I felt very sad about my life.
Someday people might not learn about how we kept things private in closures. They'll have a `private` keyword to make their transpiler do it for them. It has become less important to directly know how inheritance works, so people might be happy having a `class` syntax. There are a few times I've wanted to make an object fallback on another without making an official "class", so maybe this will be secret juju someday.
I tend to view language development like overfilled buckets. When JS becomes as complicated and unwieldly as C++, we'll spill into the next bucket in an effort to create another simple language.
Just checking -- is this a parody of a JavaScript hipster, or do you seriously get depressed by writing a switch statement?
That said, when I have to work in an older project without babel, it gets cumbersome to change into doing things the old way. You can get really used to arrow functions and async... Working without cjs modules is harder still. There are many layers of convenience, and it's easy to get used to them.
Though I still don't understand why the ECMAScript standards body keeps updating almost only syntax. They've done so little to the standard library that it's maddening. Honestly I would take zero syntax changes / improvements if it meant getting first class, JavaScript standard utilities for, say, dealing with HTTP / HTTPS.
I expect the Haskell community to s* enough bricks to build another Empire State building.
async function renderChapters(urls) {
Promise.map(urls, getJSON).each(j => j.html));
}
or async function renderChapters(urls) {
var chapters = await Promise.map(urls, getJSON);
chapters.forEach(j => j.html);
}Does such a library exist already? It seems like it'd be a very useful thing.
They were in traceur master not too long ago, but I'm not sure if they have been pulled out ([0] indicates that the proposal was pulled and replaced with the lesser Observable).