
Async/Await will make code simpler - thmslee
https://blog.patricktriest.com/what-is-async-await-why-should-you-care/
======
atombender
I wish the await syntax was inverted; instead of waiting for an async
function, let all async functions await by default. In other words, I wish
this worked:

    
    
        let foo = myAsyncFunc()
        foo.bar()
    

Why? Because that's the common case, and the caller shouldn't care whether the
function is async or not. If you have a non-async function that you want to
change to be async, or the other way around, then you have to change every
single call site.

The case where you want to store a promise or wait on multiple is actually the
edge case, and they could have reused "async" here:

    
    
        Promise.all([
          async myAsyncFunc(),
          async someOtherAsyncFunc()
        ]);
    

Bonus functionality: If you do "async foo()" and foo() isn't async, the
compiler could still ensure that it provided a promise.

Sadly, it's too late for this, and everyone's code will suffer as a result.
It's way too easy to forget to add "await" to an async call, and doing so
leads to failures that can be hard to track down.

~~~
chronial
> If you have a non-async function that you want to change to be async, or the
> other way around, then you have to change every single call site.

At least in the direction non-async -> async, that is clearly a good thing,
since you are changing semantics a lot.

This code is always safe:

    
    
        x = globalObject.a
        functionWithoutSideEffects()
        gobalObject.a = x + 1
    

While this is not:

    
    
        x = globalObject.a
        await functionWithoutSideEffects()
        gobalObject.a = x + 1
    

> It's way too easy to forget to add "await" to an async call, and doing so
> leads to failures that can be hard to track down.

This a situation that could clearly be improved with better detection of these
cases. Or maybe it should be completely illegal to call an async function
without a keyword, so you have to do either `await func()` or `promise
func()`.

------
davnicwil
I heard Doug Crockford talk about how he doesn't think async/await is that
great an idea on a podcast a while ago.

His argument was that it's an unclean abstraction - it gives you access to
'features' of synchronous imperative syntax (lines in a function always
execute in order, try-catch blocks, etc) but it remains conceptually and
literally promises all the way down.

Therefore, all await 'calls' are really non-blocking at a global level, yet
they appear blocking to the local lines of code inside the same async
function. This is liable to cause confusion - particularly for beginners or
occasional visitors to JS who don't fully grok or have the concept of promises
top of mind - but really for everyone.

I've used async/await a fair amount in production code now (with babel) and
while it does make some code a bit cleaner, honestly it is often to the
detriment of understanding it when you come back to that code it in a few
weeks. I've made plenty of stupid mistakes where the two 'faces' of the
abstraction don't marry up, and it's frustrating.

More and more I'm inclined to just use promises, even when I have the choice
of async/await - call a spade a spade and get on with your day. The article
talks about promise chains getting complex and hard to read. Well, if this is
the case, maybe it's your code or logic flows _in general_ that need to be
cleaned up, and changing the syntax to flatten the structures is actually just
a sticky plaster over that.

~~~
wahern
Here's a great article somebody posted on HN awhile back, "What Color is Your
Function?"

\-- [http://journal.stuffwithstuff.com/2015/02/01/what-color-
is-y...](http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-
function/)

The proper abstraction is threads--i.e. a stack structure used for the storage
of temporary values, which is shared by nested function invocations as well as
similar structured programming constructs like if/else conditionals that
together represent a singular thread of control through the program as it
processes data and events. "Thread of control" is precisely what you're
cobbling together with promises, async/await, etc, using clunky compiler
constructs bolted onto the language model and implementation. The problem is
that we conflate the implementation with the abstraction, sometimes by
necessity (the legacy of the contiguous, so-called "C stack") but usually
unnecessarily.

It pays to remember that some operating systems, such as some of IBM's
mainframe platforms, IIRC, implement a thread stack as a linked-list of
invocation frames, rather than a contiguous array. But it's completely
transparent, as it should be. Async/await does the exact same thing, except
it's not transparent as it should be. So now you have _two_ distinct, mutually
incompatible kinds of functions solely because the implementation is unable to
properly hide the gory details, which is ludicrous on its face.

~~~
aidenn0
light-weight multithreading that enters a scheduler loop when I/O would block
is great, and I haven't seen any advantages of async/await compared to that,
other than ease of implementation (you make the programmer or library author
implement multithreading, rather than implementing it in your runtime).

Promises are at least slightly more interesting because they can functionally
compose in various ways (but most of the time it's just a dozen then() calls
in a row, and threading would have been better).

~~~
zzzcpan
I'm sure you can agree, that not having to worry about all kinds of races,
contentions, deadlocks, wasting time on shotgun debugging, but still never
really feeling like your program is reliable enough and about other "nice"
things that come with shared memory multithreading is a huge advantage for any
concurrent program.

~~~
wahern
Multithreading is not synonymous with preemptive scheduling. That's an
entirely different issue. A chain of promises or async/await functions is no
better in this respect than so-called green threading, and in many cases is
worse than a cooperatively scheduled framework that provides more explicit
control over the points at which thread execution can switch. For example, a
system built on stackful coroutines where thread execution only occurs at
explicit resume points, or a simple message passing model where execution
changes only at the point a message is transferred. These are basically
continuation passing style, except importantly the _state_ of recursive
function invocations is completely transparent to intermediate functions,
without having to explicitly pass around state or annotate your function
definitions. In other words, no different than how you'd write any other
function.

That's my point. The better _abstraction_ for all these things is a thread, no
matter how you choose to schedule chunks of work or how you choose to
implement things under the hood. A thread is just a construct that encompasses
nested function invocations, and that construct is what promises and
async/away emulate, except that that they leak implementation details and
restrict the normal things functions do, like call other functions.

~~~
zzzcpan
Here's the thing, if you can call functions that themselves can yield - you
are in a shared memory multithreading model, where you can never guarantee for
any function not to yield, so you have to use synchronization for that
guarantee with all the same issues.

~~~
aidenn0
In a cooperative multi-threaded model you can only have data-races at
function-call boundaries. For example: `x+=1` can never data-race.

~~~
ngrilly
Except if your programming language allows you to override the += operator.

~~~
aidenn0
You are proposing a situation where someone overrides += to specifically both
call a blocking function _and_ to not make it work correctly. I'm not saying
it doesn't happen, but bad code is bad code regardless of your paradigm.

Though I must admit I've not done cooperative multitasking in a language with
operator overloading so I can't say whether or not this is a problem in
practice.

~~~
ngrilly
In can happen for example in Python with gevent.

------
dlbucci
I was super excited about async/await when it first came out. I hadn't really
understood the point of Promises, but async/await looked simple and useful.

However, I recently started using async/await in TypeScript, and the result
seems to be try/catch statements everywhere. Code using async/await seems to
be more verbose and unruly than just sticking to Promises, which I now
appreciate the elegance of much more (callee convenience/error delegation).

I think I'm just going to stick to Promises for the time being, until I see
some hidden usefulness of async/await syntax (which could totally happen. It
took me a long time to realize how awesome promises are).

~~~
treve
This makes little sense. You don't need more try..catch blocks than you would
normally have a catch promise clause.

Exceptions bubble up.

~~~
duncanawoods
I'm finding that with client side async, the awaited behaviour is often
external, heterogeneous and unreliable. When an operation fails, it typically
requires a state-machine transition to a failed/retry state or fallback
service rather than just bubbling up to a top level handler like a coding
error i.e. there is something specific you need to do unlike typical
exceptions.

Retrofitting a code base with async I concur that local try / catch has been
necessary and has added code complexity and doesn't always feel like an
improvement. In contrast, server side async has been much more elegant because
the errors with internal async server operations are much more exceptional and
don't require so much case specific handling.

~~~
codedokode
You should make your API client layer be able to retry operations (and maybe
track the status of a network connection) rather than write catch/then()
manually.

~~~
duncanawoods
It would be nice if it was that easy but if retry behaviour is dependent on
the specific operation attempted and the error information in the response
then you need some degree of local handling even if its just to prepare
information for a generic handler.

Exceptions work best when the error is fatal for the local scope but responses
from external services aren't like that. The general problem is that the
dividing line between errors and information becomes too blurry - your error
might only be information to me. A simple example is where something like
axios will (by default) throw on 404 responses but an external api might use
404 to indicate a resource does not exist. If your app logic makes a decision
based on this information, you will find yourself using exception handlers for
control flow despite not experiencing any actual errors.

------
tarr11
Though the article doesn't mention it, the "alternative" is Reactive
programming (via RxJS) I think for the typical UI application developer, async
/ await can provide easier readability and debugging, but at the cost of some
expressive power and conciseness.

Now that chrome supports async / await in the debugger, it's almost certainly
the best choice, compared to promises and callbacks.

In the redux world, you can see this by comparing redux-observable [1]
(reactive / rxjs) with redux-saga [2] (generators)

Rxjs requires a deeper understanding of Reactive programming. Once you
understand it, you can write very powerful expressions in a few lines. But
debugging is tough and it doesn't translate well for your fellow developers.

[1] [https://github.com/redux-observable/redux-
observable](https://github.com/redux-observable/redux-observable)

[2] [https://github.com/redux-saga/redux-saga](https://github.com/redux-
saga/redux-saga)

~~~
jrs95
Only downside I've found with this is that Observables feel like more of a
pain to test, since your logic gets more tightly coupled to your I/O. Or at
least there's more complexity involved in the relationship between I/O and
data manipulation. I used them for a Node project via RxJS and ended up just
switching back to promises, as it wasn't complex enough of a project to really
see much of a benefit from Observables.

~~~
rounce
The only difference between promises and observables are multiple-emission &
cancelation, they shouldn't be any more complicated to use or test than
promises. Often I find the main complicated part is the source, everything
else is just filter/map functions, sometimes to more streams. All of these
individual units are more easily described/tested in comparison to the entire
chain (and even then you are only caring about the ends of it).

------
mpweiher
I go back and forth on async/await. On the one hand, it is utterly brilliant.
On the other hand, it seems like the final epicycle, trying to fit a theory of
circular geocentric orbits (call/return) onto a real world of elliptical
heliocentric ones (asynchronous programming).

So yes, it will make code easier, but I fear that will only serve to prolong
the dominance of what is arguably the wrong programming model/architectural
style. Or more precisely: an insufficient programming model/architectural
style (it is great for a lot of things, just not for all).

~~~
throwasehasdwi
await/promises/etc mostly exist to solve the problem that JavaScript doesn't
have threads so it can't wait for callbacks.

About JavaScript, many other languages have had async/await for a long time. I
have no idea why JS made such a huge deal of promises, I guess they're better
than the callback hell before. Of course, in most languages using async isn't
nearly as important for performance because they have thread pools.

Some interfaces aren't and won't be asynchronous (like Linux file IO) so
eventually JS will support proper threads and we can stop talking about how
great asynchronous programming is (it isn't).

~~~
jrs95
Node has threads in C++ for that sort of thing. Async programming is a big
deal for performance. That's essentially the main reason Node is any faster
than Python. If you use fully async Python on uvloop, you can get comparable
performance.

~~~
balfirevic
It is ultimately a failure of language and runtime that programmer has to
manually specify where he wants to make asynchronous vs. synchronous functions
to get the optimal performance.

This blog posts elaborates on that better then I could do here, so I'm just
going to link to it: [http://journal.stuffwithstuff.com/2015/02/01/what-color-
is-y...](http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-
function/)

~~~
jcelerier
> that programmer has to manually specify where he wants to make asynchronous
> vs. synchronous functions to get the optimal performance.

programs aren't just pure computations. There are plenty of times when you
want a specific event to happen at a specific time (as in, wall-clock), and
plenty of times when you don't care when something computes as long as you end
up getting a result at some point.

~~~
throwasehasdwi
You don't get reliable wall clock time unless you're working in RTOS. In a
threaded OS everything in userland is async to an extent. In this school of
thought, having to specify that something should be async manually could be
seen as a failure of the language.

~~~
jcelerier
on recent good hardware there is no problem being around 1ms accuracy.

------
yarg
I really don't see the need for such a syntax. If I say:

    
    
      Foo foo = someFoo();
      ...
      String content = foo.getContent();
    

I really don't care if foo was returned when I called someFoo(), I only care
that getContent() is not attempted until after foo is defined.

There's little reason for me - in this situation at least - to need this async
functionality to be explicitly stated.

The biggest problem I see is a when you use this functionality in a loop of
independent executions (when the results of any given loop don't depend on the
results of prior loops):

    
    
      for(Foo foo: asyncFoos()){
          Foo foo = someFoo();
          ...
          result.add(foo.getId(), foo.getContent());
      }
    

The problem here is that the use of a for loop will introduce latency,
potentially dramatically increasing execution time.

Now you can either replace this "for" with some sort of "for-each" type of
block, or you can go async all the way and treat "for" as "for-each" and any
referenced result of a prior iteration as yet another async value.

That covers sets and singletons, I imagine that any other situation that needs
to be dealt with can also be covered on the compiler side of things with only
minimal changes to syntax.

~~~
whipoodle
You may not care, but the computer does!

~~~
miguelrochefort
Why doesn't the computer do it for me?

~~~
whipoodle
Agreed.

------
mvindahl
I really like the succinctness of async/await, and I feel like it's the last
step of a long journey. The single threaded callback based style of Javascript
has always been a mixed blessing. It has allowed for great performance, and it
gives better control that having to juggle threads, but it was all to easy to
get into deeply nested callback hell.

If you see design patterns as indicators of language smells -- and it's a
useful perspective IMHO -- then in this case callbacks were the smell and the
various promise libraries were the design patterns. It's a nice thing that
promises and their async/await sugercoating have fairly rapidly made it into
the core language.

Now, for the one thing that I don't like about async/await: it's deceivingly
simple and it's bound to fool both novice programmers and programmers arriving
from threaded languages. If you don't understand the underlying concept of
promises, you'll easily end up writing suboptimal code (e.g. executing stuff
in sequence which would be better executed in parallel).

Still, a net win.

------
hitgeek
same example with async control flow library and node style callback
conventions

function getUserInfo(callback) { async.parallel([api.getUser, api.getFriends,
api.getPhoto], callback) }

~~~
askmike
Even with a library like async there will be so much boilerplate, since you
need to handle (or pass) errors every step of the way.

With async/await you only need to deal with errors at the level you actually
care about them (using the language build in try/catch block).

~~~
yesbabyyes
In the example above, any error will be passed to the callback sent to
getUserInfo - i.e., where you care about it.

------
stevedekorte
Async/await can be used to implement coroutines, but IIRC only if you wrap all
of the code that might be called in async/await as well, and this catch makes
them practically useless. Why not just provide proper coroutines?

~~~
hdhzy
Because people don't need coroutines, just a simpler way to write async code
than callbacks and manual promises.

While we're at it why coroutines? You can implement coroutines, generators,
even try/catch and return given continuations [0].

[0]: [https://curiosity-driven.org/continuations](https://curiosity-
driven.org/continuations)

------
fergie
Not a gigantic fan of async/await for the reasons that others have mentioned
here, but that said it provides the only sane out-of-the-box way for JS to
handle loops that contain callbacks/promises.

------
imperio59
Anyone else bothered by the utter lack of semicolons in the example code? :D

~~~
askAwayMan
If you've got a halfway decent linter, semicolons are just clutter.
[https://eslint.org/docs/rules/no-unexpected-
multiline](https://eslint.org/docs/rules/no-unexpected-multiline) is the sort
of thing that makes semicolon free style practical.

~~~
Bahamut
There is one situation where semicolons are important - when concatenating
multiple script files & using IIFEs.

This subtlety is being obsoleted by ES modules & the build tooling around
them, but it is a nasty bug that has sucked many an hour away from frontend
devs whenever it is encountered.

------
miguelrochefort
I much prefer observables to tasks...

------
jaequery
not too long ago, async used to be the "thing".

and now sync is the new "hot stuff".

a bit ironic but at the end of the day, simplicity always wins.

------
krono
try catch try catch try catch try catch try catch try catch try catch try
catch

~~~
coldtea
Or, you know, a single try catch. Or several of them. At any level you like
and fits the problem. And no lost exceptions.

~~~
krono
You're right and async await is much nicer but all the try catch blocks are
slowly getting to me

