
ES7 async functions – a step in the wrong direction (2015) - qubitcoder
https://spion.github.io/posts/es7-async-await-step-in-the-wrong-direction.html
======
skrebbel
This argument basically boils down to "I think elegant abstractions are more
important than readable code". This is a pretty valid position to hold, but I
strongly doubt the majority of the JS community agrees.

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.

~~~
tracker1
I have to agree... just because someone may have a case where they need multi-
stage transactions or cancellable workers doesn't mean that Promises and
async/await aren't useful as they stand.

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.

~~~
spion
Not sure what you mean by multi-stage transactions? It applies for ordinary
transactions - its just a way to avoid passing a transaction parameter
everywhere but still guaranteeing that the entire batch of queries executed
within a single transaction.

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.

------
edgyswingset
> Cannot use higher order functions

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.

------
cromwellian
Yes, there are some edge cases, but the reality is, I've seen code festooned
with Promises, and code using async/await, and I find the async/await code far
more readable.

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.

------
kenOfYugen
> Generators are JavaScript's programmable semicolons. Lets not take away that
> power by taking away the programmability. Lets drop async/await and write
> our own interpreters.

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](https://github.com/ubolonton/js-csp)

[2] [http://jlongster.com/Taming-the-Asynchronous-Beast-with-
CSP-...](http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-
JavaScript)

[3] [http://jlongster.com/Transducers.js--A-JavaScript-Library-
fo...](http://jlongster.com/Transducers.js--A-JavaScript-Library-for-
Transformation-of-Data)

~~~
spion
While we're at nicking stuff from Go using generators, our latest effort is
adding defer in bluebird :)

[https://github.com/petkaantonov/bluebird/issues/1014](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...](https://github.com/petkaantonov/bluebird/commit/f944db753ff25e43561fb6c65a6664417b9892cd#diff-3129285757f0c44eca9973432b3bb15aR704)

------
amasad
Interesting write up, examples, and ideas, but the conclusion is a bit silly:

>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...](http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-
function/)

~~~
spion
> Guess what kind of "interpreter" most people would need? The async/await
> kind.

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.

------
spriggan3
> Lets drop async/await and write our own interpreters.

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?

~~~
carussell
Some form of catch guards have been implemented in Firefox for over a decade,
so if you were running SpiderNode, you could use them right now.

    
    
        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");
        }

~~~
tracker1
Well, it's entirely possible to submit that as a stage-0, then it could get in
babel and be used sooner than later.

------
scotty79
If language has primitives for some concepts then people tend to use those
because they have special syntax that makes using them more convenient.

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.

~~~
esailija
Async/await doesn't only work on promises, it works on any object that has a
method named `then`. What's problematic is that you have no control or
extension points in the runner so you cannot implement something like Go's
defer statement. With generators (which can be made work like async/await,
only you say yield instead of await) you can implement the defer statement and
more.

~~~
scotty79
Good that it works on any object with then. Shame that it's not more flexible.

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.

------
mbell
> And voila, we've just implemented a query optimizer. It will fetch all issue
> owners with a single query. If we add an SQL parser into the mix, it should
> be possible to rewrite real SQL queries.

Maybe my sarcasm detector is off but is this really being presented as a
reasonable solution to the problem discussed in the article?

~~~
spion
Sorry, I'm not being quite clear there - I'm just trying to illustrate that a
programmable execution engine could have many uses (one of them being the
ability to automatically optimize the n+1 queries problem).

------
mbrock
I like when languages are small and simple. The overlap between generators and
async/await seems a little strange. So my feeling is that the "wrong
direction" is towards language complexity.

------
jonesb6
Cool I guess. But people will still find a way to write bad ES7. Will it be
harder to do? Maybe. Hard to tell until it gets implemented by all major
browsers, at which point i'll have given up programming for professional hover
board racing sponsered by ubersoft (microsoft got aquired after the second
robot uprising).

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?

~~~
coroutines
I primarily work in Coffeescript.

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.

~~~
pavlov
_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._

Just checking -- is this a parody of a JavaScript hipster, or do you seriously
get depressed by writing a switch statement?

~~~
tracker1
I can't speak for the gp, but I get irritated when I can't use newer language
features... pretty much everything I use is in stage-2 supported via babel at
this point, so pretty happy...

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.

------
BinaryIdiot
Honestly I would prefer this to promise. Yeah promises are nice and all but I
keep getting myself into projects where promises are nested _even worse_ than
callback soup which ultimately ends up just confusing me because it's not
simple and straight forward anymore.

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.

~~~
MrOrelliOReilly
You mean like [https://developer.mozilla.org/en-
US/docs/Web/API/Fetch_API](https://developer.mozilla.org/en-
US/docs/Web/API/Fetch_API) ?

~~~
BinaryIdiot
No because that is only for the web. It's not part of the ECMAScript standard.
There are packages to make it work in node and hacks to make it work on more
browsers. But no standard.

------
sotojuan
It seems like every time something nice and new comes to JS it's immediately
called bad.

~~~
EvanPlaice
Just wait until the :: function bind operator is added.

I expect the Haskell community to s __* enough bricks to build another Empire
State building.

------
nikki93
Wait, so are you saying that async functions are a step in the wrong
direction, or are you actually saying that you like them but would want to add
features / do it better?

------
Akkuma
Using a library like Bluebird couldn't you simply write and get the exact
behavior intended?

    
    
        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);
        }

------
true_religion
> Since promises are a userland library, restrictions like the above do not
> apply. We can write our own promise implementation that demands the use of a
> predicate filter

Does such a library exist already? It seems like it'd be a very useful thing.

~~~
esailija
[http://bluebirdjs.com/docs/api/catch.html](http://bluebirdjs.com/docs/api/catch.html)

------
carapace
To me this sort of reads like someone discovering lisp (but in JS.)

------
jsprogrammer
What you're looking for is Async Generators [0].

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).

[0]
[https://github.com/jhusain/asyncgenerator](https://github.com/jhusain/asyncgenerator)

