[HN Gopher] I Avoid Async/Await
___________________________________________________________________
I Avoid Async/Await
Author : kiyanwang
Score : 149 points
Date : 2022-04-16 08:24 UTC (14 hours ago)
(HTM) web link (uniqname.medium.com)
(TXT) w3m dump (uniqname.medium.com)
| moron4hire wrote:
| I try to read every one of these sorts of articles that comes up.
| I don't want to be caught flat-footed on some kind of weird
| memory leak issue from not using a pattern correctly, or
| something.
|
| But every single one of these articles that I've read against
| async/await has been nothing more than inventing "problems" to
| try to convince people to stick to raw promises strictly because
| that's what the author is comfortable with using. No actually
| technical merits are discussed. Just fantasy issues so author can
| feel superior for _not_ learning something new.
|
| Like how the author claims you can't visually "see" that two
| await calls can be parallelized. Speak for yourself, buddy. And
| then complains about "having to go back to using promises" to
| affect the parallelization. My dude, it was promises all along.
|
| It's just syntax. Use it, don't use it, mix and match it. It's
| not a moral issue.
| gombosg wrote:
| Yup, I agree that the author is missing the point here. You can
| mix these syntaxes and have, say, "await Promise.all" without
| guilt.
| SideburnsOfDoom wrote:
| > you can't visually "see" that two await calls can be
| parallelized. Speak for yourself, buddy.
|
| Agreed. My first thought was to try var
| userSaveTask = save('userData', userData); var
| sessionSaveTask = save('session', sessionPrefences);
| await Task.WhenAll(userSaveTask, sessionSaveTask);
|
| I saw it before I got to the part of the text explaining how I
| wasn't going to just see it.
|
| > Furthermore, if we are to take advantage of parallelization
| in the async/await example, we must use promises anyway.
|
| Again, Speak for yourself, buddy. "await Task.WhenAll" does
| that job in c#, and there must be a JavaScript equivalent.
|
| IDK, this may be the c# mindset. yes, "async / await" is an
| extra-ordinarily complex feature that can easily be done wrong.
| But its also very useful and powerful, so lets not throw it
| out.
| chrisco255 wrote:
| Yes, it is `await Promise.all()` in JS.
|
| https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
| johnny22 wrote:
| > Like how the author claims you can't visually "see" that two
| await calls can be parallelized.
|
| I'm not gonna pretend to be a JS expert here, but isn't that
| exactly what you can see? Isn't that the whole point? It lets
| you easily take a second look and decide to check if the
| operations can happen in parallel or not.
|
| I'm literally using 2 awaits in a row because android's BLE
| implementation doesn't necessarily like more than 1 operation
| happening at once, and I'm not doing enough other operations
| such that I need to implement a whole queue to handle it.
|
| (although maybe the library i'm using should implement that
| queue)
| __ryan__ wrote:
| This article takes serious mental gymnastics to follow.
|
| The main argument seems to be that an engineer with a working
| mental model for basic promise chaining will be unable to
| translate that to async/await, which is effectively syntax sugar
| for the same thing.
|
| The author says that multiple calls to _< promise>.then()_ is a
| cue that the code is occurring serially, and the new keyword
| _await <promise>_ somehow isn't.
|
| There's a time and place for raw promises, but this article
| hardly touches on anything actually wrong with async/await.
|
| I've worked with engineers who actively fight against learning
| their tools, like this. It's a nightmare. I don't trust them.
| Don't be that person.
| [deleted]
| Aeolun wrote:
| > It's because we are taught to read async/await code in a
| synchronous mindset. We can't parallelize the save calls in that
| first fully synchronous example, and that same -- but now
| incorrect -- logic follows us to the second example.
|
| What a weird idea. When I see a serial bunch of awaits the first
| I think of is whether that makes any sense.
|
| I think the author is kind of projecting his own views on
| everyone else here.
| lr4444lr wrote:
| YOU may, buy your core frameworks may not. And if your
| synchronous code is holding up async loops, you're gonna
| potentially create a lot of very hard to diagnose problems.
| Beware the foundation on which you build.
| andix wrote:
| I always do await Promise.all(...)
|
| And now?
| jrochkind1 wrote:
| In that first example, `await` means exactly the same thing as
| `then` for the purposes the author is talking about -- realizing
| that something had been serialized that could have been done in
| parallel.
|
| I don't see why most engineers would be likely to recognize that
| opportunity when written as `then` but not as `await`. If is is
| true (and I'm not convinced it is), it seems like an education
| problem rather than a reason to use promises rather than await.
| It doesn't seem fundamentally harder to know that two `awaits` in
| a row means serialization than to know that two `thens` in a row
| does.
|
| At least in that simple example where the use of `then` is
| exactly parallel to the use of `await`. Perhaps it wasn't a good
| example, and it really would be harder to reason about in a more
| complicated example.
| rwieruch wrote:
| I always wondered why async/await introduced a concise way of
| dealing with asynchronous code, but then breaks all this
| conciseness with try/catch ...
|
| Hence I tried to combine async/await with catch for an improved
| readability [0] two years ago. Turns out I never used this
| approach, because it's not common sense when working in a team
| and still feels too verbose. Either one uses then/catch or
| async/await with try/catch. However, I still feel try/catch still
| makes code unattractive.
|
| [0] https://www.robinwieruch.de/javascript-async-await-
| without-t...
| BigJono wrote:
| I've never found this to be much of a problem in good code. API
| failures in front-end code tend to fall into a couple of
| buckets that can be abstracted away. If you want to preserve
| the page and keep retrying with exponential backoff, you can
| just implement that as a function that you then await until it
| succeeds. If you can't recover and just want to show the user a
| "shit's fucked, refresh the page" banner then you just need one
| handler near the top of the stack and let the errors bubble up.
| Most people are working in a declarative environment like React
| where you can just have a hook wrapping your API calls that
| exposes a potential error state to render accordingly.
|
| You only really need to have try/catch blocks in 1 or 2 places
| unless you have a lot of novel async stuff going on that needs
| to recover from failures in specific ways. Most front-end code
| shouldn't have a try/catch around every API call.
| kareemsabri wrote:
| The premise of this article seems flawed. We don't "code in a
| synchronous mindset" when we use async/await. I would expect most
| programmers are aware, after a 5 minute google search, that there
| is an asynchronous process happening and we need to ensure no
| execution after that line occurs until that process has resolved,
| hence we use await. If you know what it is doing, it's not
| complicated at all. It's identical to promise chaining but less
| ugly.
| daxfohl wrote:
| Ultimately the problem of potentially missed opportunities for
| parallelism there in regular sync code too. If we take the
| author's point seriously, we should all be programming in a
| language that requires explicit continuations everywhere just
| to guard against accidentally serializing things.
| pausmfib wrote:
| I understand that the author is more familiar with the syntax of
| promises. Nonetheless, having a syntax similar to synchronous
| code allows developers to write more code remembering only the
| couple of changes in syntax instead of having to memorise a
| different one.
| justsomeuser wrote:
| I don't think of async await as "making async code act like sync
| code".
|
| I see it as adding meta data to a function type (async) and
| allowing you to attach the result to a given function scope
| (await).
|
| The key utility of this is to allow you to use functions as a
| unit of composition, allowing you to leverage their already built
| tooling (IDE jump to function, stack frame based debugging, input
| and output types (in:args -> out:return).
|
| Messaging runtimes like Golang and Erlang use an additional
| composition unit (channels and mailboxes), and do not allow you
| to follow the "tree of functions" paradigm like async await does.
| These result in having to follow a network graph of messages and
| nodes to find out what will happen.
|
| You need to understand you are dealing with an event loop before
| you use async await. I think this is the issue the author is
| raising.
| phendrenad2 wrote:
| Async/await was the latest buzzword a few years back, but it
| never had any substance. A lot of inexperienced programmers
| thought it was the greatest thing ever. Unfortunately, they were
| overly focusing on the look of the code, and not the true
| readability or usability of the code. Async/await certainly makes
| you code "look" synchronous, but it's just shuffling the
| complexity around. The complexity is still there, and you will
| have to think about it.
| stupidcar wrote:
| > The keywords async and await should give us the same intuition
| that the then does in the third. But I'll wager for many
| engineers it doesn't.
|
| It does.
| AWebOfBrown wrote:
| Agree that try/catch is verbose and not terribly ergonomic, but
| my solution has been to treat errors as values rather than
| exceptions, by default. It's much less painful to achieve this if
| you use a library with an implementation of a Result type, which
| I admit is a bit of a nuisance workaround, but worth it. I've
| recently been using: https://github.com/swan-io/boxed.
|
| By far the greatest benefit is being able to sanely implement a
| type-safe API. To me, it is utter madness throwing custom
| extensions of the Error class arbitrarily deep in the call-stack,
| and then having a catch handler somewhere up the top hoping that
| each error case is matched and correctly translated to the
| intended http response (at least this seems to be a common
| alternative).
| eyelidlessness wrote:
| Long rant is long, so I'll leave a tl;dr: regardless of how you
| feel about the syntactic sugar aspect, async/await has a runtime
| advantage of suspending the stack rather than exiting early,
| which is (IMO) of greater benefit than the syntactic improvement.
|
| - - -
|
| I'm in the process of gradually moving several decade-old JS
| projects from explicit Promise APIs to async/await. The reason
| for this is _not_ the syntactic sugar (although that's quite a
| benefit, and I disagree wholeheartedly with the article on that
| point). The reason is that, at least with native Promises, the
| actual runtime behavior of async /await is considerably better.
| Because await suspends the stack, where .then/.catch enter a new
| stack frame.
|
| I do see this has been discussed some in another thread, but I
| feel like it deserves more direct attention. It's true that you
| can use libraries like Bluebird or whatever to get more accurate
| stack traces. But this is done by tracking Promise chains in user
| space. This is fine but it comes with a cost. Even if the library
| is quite fast (as Bluebird is), you still incur the cost of
| downloading and parsing it. Especially on HN where JS bloat is a
| daily topic, I'd hope this can be appreciated.
|
| In my case, this cost wouldn't be worth it even if the Promise
| API were clearly better. These projects need to run efficiently
| on low power mobile devices with limited connectivity. And we
| need to be able to diagnose runtime errors to address outstanding
| bugs. A library is a non-starter, and native Promise APIs
| actively hinder progress. The behavior of async/await is clearly
| superior.
|
| Suspending the stack, either with async/await or generators,
| affords significantly better debugging capabilities. It allows
| Error stacks and console.trace to take better advantage of source
| maps.
|
| It's also a much better optimization target for browsers. This
| isn't just academic: even if async/await is semantically
| equivalent to Promise, it's not a guarantee that the underlying
| .then/.catch will even be called if there's no explicit Promise
| in the code. I've seen cases where they're not, which was
| baffling until I understood (and this is how I came to
| understand) the difference in runtime behavior.
| steve_taylor wrote:
| I'd like to see the author rewrite a for-of loop that contains
| async code in the body as traditional promise code, then look me
| in the eye and tell me the for-of version is more awkward.
| adontz wrote:
| I believe author misses one important point.
|
| Async/await optimizes for throughput, not latency. Use case is
| many lightweight tasks you want to parallel as wholes. Web APIs,
| web sites are usual examples.
|
| However, if you need other kind of optimizations, like two
| parallel saves within one request, maybe async/await is not what
| you need in the first place.
| jrochkind1 wrote:
| The performance characteristics of promises and async/await are
| identical, no?
| adontz wrote:
| Yes, however capabilities are not the same. Promises are
| lower level primitives.
| zkldi wrote:
| they are. async functions sre just syntactic sugar for
| returning promises.
| recursivedoubts wrote:
| I work on a web scripting language that abstracts away
| asyc/await:
|
| https://hyperscript.org/docs/#async
|
| we call it "Async Transparency"
|
| It lets you write code like this: fetch /some-url
| as json put the result's content into me
|
| where fetch is an async call, but you don't have to await it or
| anything or mark it as being an async fuction, whathaveyou
|
| effectively, we de-color the language:
|
| https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
|
| my feeling is that async concerns are too low level for light
| scripting
| zkldi wrote:
| How would you allow running the fetches in
| fetch /some-url as json fetch /some-other-url as json
| do_stuff_with(result1, result2)
|
| in parallel?
| recursivedoubts wrote:
| hyperscript is focused on the common cases for light front
| end scripting, so right now there isn't syntax for doing
| operations in parallel.
|
| If I needed that I would kick out to javascript and use
| Promise.all() to return an expression that hyperscript could
| then sync on
|
| the wheelhouse for hyperscript is stuff like:
| on load wait 5s transition my opacity to 0
| remove me
|
| where you don't have to do any async or callback stuff for
| what are, at root, async operations.
| z3t4 wrote:
| I understand there are two sides/groups here: 1) Those who think
| errors are important to control flow, and 2) Those who think
| errors are exceptions to control flow. If you are in group 2 both
| Promises and async/await will give you neat and simple code. But
| if you are in group 1 Promises and async/await will be really
| complicated and ugly because each await will be inside a
| try/catch.
|
| Because I'm in group 1, I try to avoid Promises (and thus
| async/await) in JS because the promise will capture all future
| errors, and if you forget to add a .catch, that error will never
| surface. Promises make asynchronous code more complicated.
| Async/await however get rid of a lot of the complicated syntax,
| so when I do not care about errors (eg. errors are exceptions) I
| use async/await because of easier control flow.
| z3t4 wrote:
| Those who think of errors as exceptions can further be divided
| into group 2a) Those who prefer Promises syntax. And group 2b)
| Those who prefer async/await syntax.
|
| Also don't forget about co-routines:
| co(function* () { var user = yield getUser();
| var comments = yield getComments(user); });
|
| Just replace "co(" with async, and yield with await, and you'll
| have async/await.
| jseban wrote:
| Errors are an important part of the control flow, if you are
| doing UI programming, which is what Javascript is made for.
| qudat wrote:
| I was really hoping this article would discuss the benefits of
| using raw generators over async/await.
|
| By using async/await, we are inherently limited by the flow
| control because we are forced into await resolving promises.
|
| This is why libraries like redux-saga[0] or cofx[1] use
| generators.
|
| https://redux-saga.js.org https://github.com/neurosnap/cofx
|
| Generators provide much better flexibility over flow control and
| allow us to treat side effects as data.
| chmod775 wrote:
| First off, async/await _is_ promises. It 's merely syntactic
| sugar. The point of async/await is not to _never_ have the word
| "Promise" appear in your code. It's also not meant to be
| universally better than using Promises bare-bones.
|
| A lot of the argument appears to be the author extrapolating from
| his own lack of familiarity to others: "We are taught", "our
| minds", etc. I can easily construe some hypothetical person with
| a certain set of skills (and lack thereof) that would have just
| as much trouble with bare-bones Promises. But I don't have to
| because the author did it for me at "One more thing...".
|
| "People can mess this up" was never an argument. You can mess
| everything up. The more interesting question is how badly and how
| often.
| Waterluvian wrote:
| TypeScript even forces you to acknowledge this as the explicit
| return type of an async function is a promise.
| phendrenad2 wrote:
| > First off, async/await is promises. It's merely syntactic
| sugar. The point of async/await is not to never have the word
| "Promise" appear in your code
|
| Sounds like you didn't understand the point of the article.
| Unfortunately, you're arguing against something the author
| never said, the author never said that async/await weren't
| promises. You just got stuck on the fact that the author used
| the term "promise" for the non-async/await style of code. I
| understood what the author meant, anyway.
|
| > "People can mess this up" was never an argument
|
| Unfortunately, you're arguing against something the author
| never said here, also. The author is saying that non-
| async/await code is better code than async/await. Not that
| "people can mess this up" or something.
| chmod775 wrote:
| > Sounds like you didn't understand the point of the article.
| Unfortunately, you're arguing against something the author
| never said, the author never said that async/await weren't
| promises.
|
| Well. I never said that I didn't understand the article.
| Unfortunately you are arguing against something I never said.
| Funny how that works.
|
| > The author is saying that non-async/await code is better
| code than async/await.
|
| And what does "better" mean to the author?
|
| The entire first section is devoted to the author talking
| about criteria such as "brittle"-ness, "error-prone"-ness,
| and "footguns".
|
| You seem to be getting stuck up on the fact that the author
| never used the exact same wording as I did.
| spankalee wrote:
| async/await is _safer_ too, because part of that syntactic
| sugar is wiring up exceptions correctly and ensuring that a
| function only returns or rejects asynchronously and never
| synchronously. This prevents lots of bugs.
|
| This article is bad, bad advice.
| whatdidiexpect2 wrote:
| It's just another willfully ignorant post by a mediocre
| JavaScript developer. Are you shocked?
| noahtallen wrote:
| eurasiantiger wrote:
| This, very much this. async function() {}
|
| is much nicer than function() {
| return new Promise( (resolve) => resolve()
| ) }
| kabes wrote:
| I agree but second one could just be `() =>
| Promise.resolve()`
| adamauckland wrote:
| It could, but depending on how you use it, you're changing
| scoping.
| kabes wrote:
| True. But these days it's rare to see a scope other than
| what you'd expect from the short hand version. Old school
| JS with plain prototypes and flexible 'this' scope is
| very powerful, but is too hard to understand and thus
| rare these days.
| bin_bash wrote:
| I prefer async/await but this is a contrived example.
|
| For starters you can just write: const
| myfunc = () => new Promise(resolve => resolve())
|
| or Promise.resolve()
| Aeolun wrote:
| const myfunc = () => new Promise(resolve => resolve())
|
| Is there anyone that actually likes this structure? I find
| it really hard to reason about what a line like this does.
| What is the upper limit on double arrows in one line?
| albedoa wrote:
| Your gripe is that there are two arrows on one line? The
| second is an argument to the promise constructor. You
| should be able to reason about this line easily.
| bin_bash wrote:
| here's a real world example I use all the time:
| const wait = (ms = 500) => new Promise(resolve =>
| setTimeout(resolve, ms));
|
| then: async function () {
| await wait(1000); }
|
| Perhaps it's hard to grok but it sure is a handy one-
| liner.
|
| Of course in newer versions of node we can now do:
| import { setTimeout, } from
| 'timers/promises'; await setTimeout(100);
| love2read wrote:
| this works in older versions of node too:
|
| const wait = require('util').promisify(setTimeout)
| oauea wrote:
| but not in the browser
| joe_guy wrote:
| There's no merit of what should be a project wide
| function being a single line instead of 3 to avoid "hard
| to grok" syntax.
| deschutes wrote:
| Wait till you see the rest of javascript...
| eurasiantiger wrote:
| Your comment is snarky and categorically dismissive. Care
| to explain why you have taken such an aggressive stance
| against JS?
| eurasiantiger wrote:
| It's the cleanest way to wrap a event-emitting library,
| for example. async/await is not applicable in such a case
| where resolve needs to be explicitly called in a
| callback. async function process() {
| return new Promise((resolve, reject) => {
| emitter.on("error", (err) => reject(err));
| emitter.on("end", (result) => resolve(result));
| emitter.start(); }); }
|
| async is not strictly needed here, but I find it a good
| practice to use it on functions which immediately return
| a Promise anyway.
| [deleted]
| zkldi wrote:
| I don't think it's a good practice, atleast if you have
| typescript jn your stack. It's redundant and wraps the
| promise in another promise.
| eurasiantiger wrote:
| IIRC that doesn't actually happen if a native Promise is
| returned.
| eyelidlessness wrote:
| It does, but only if you also add a redundant await.
| Which I still do, even in TypeScript, unless there's a
| compelling performance reason not to. I disagree with the
| article overall, but I do agree that making asynchrony as
| explicit as possible is a good idea. Otherwise you end up
| with code like: async function foo(bar) {
| // ... } function nonObviousAsyncFn() {
| // ... return foo(quux); }
|
| Explicit return types would help, but most people don't
| write them unless they're forced by a linter.
| wildrhythms wrote:
| As soon as you need to chain Promises, this gets very
| annoying and you quickly find yourself in a nested callback
| hell.
| bin_bash wrote:
| If you're nesting "new Promise" you're almost certainly
| doing it wrong. This is for generating new promises not
| executing them.
| dataflow wrote:
| new Promise() is not how you want to create a promise in JS
| (except when you really have to), just like 'new'/'delete'
| are not how you ant to allocate memory in C++ (except when
| you really have to). There are lots of helpers in that class
| that make promises significantly more ergonomic.
| michaelsalim wrote:
| Care to give some examples of the helpers? I always just
| use new Promise()
| aGHz wrote:
| You really end up creating promises manually, the vast
| majority are downstream from an IO call like fetch() or a
| database query.
| eurasiantiger wrote:
| Many devs unnecessarily nest Promises like that.
| async function getData() { return new
| Promise((resolve) => { const res =
| fetch(...); resolve(res); });
| }
|
| The response above is wrapped in THREE different
| Promises! One from fetch, one manually created, and one
| implicitly created by `async`.
|
| The code above behaves exactly the same as
| function getData() { return fetch(...);
| }
|
| or even just fetch(...);
| Cpoll wrote:
| > function getData() { return fetch(...); }
|
| I feel like there are advantages to making it `async
| function`, even if it's superfluous, because it signals
| to readers and to static code analysis that the function
| returns a promise. That's assuming the return type of
| fetch(...) can't be inferred by static analysis and
| developer tooling.
| Kwantuum wrote:
| > the type of fetch() can't be inferred by static
| analysis
|
| That is preposterous.
| Cpoll wrote:
| It's not. One might assume it's Node fetch, but parent
| post was just using a specific example to make a general
| point. Fetch can be `any`thing.
|
| Would you say the same thing about
|
| function getData() { return mysteryFunction(...); }
| RobertWHurst wrote:
| I've been on teams working on node projects for like 15
| years. I've never seen someone who understands promises
| or async await do that. You'd have to know nothing about
| what the async keyword actually does to be compelled to
| do that.
| hn_throwaway_99 wrote:
| I mean, I know its a bit of linguistic flourish, but
| considering NodeJS was only initially released 12 years
| ago, that's quite a feat!
| eurasiantiger wrote:
| I have seen plenty of (usually junior front-end) devs
| resort to exactly this kind of cargo cult coding and
| magical thinking.
| wiml wrote:
| Well, they're junior devs. You're supposed to help them
| get past that stage, not just throw away all your tools.
| eurasiantiger wrote:
| What made you think I haven't been helping them?
| joe_guy wrote:
| I'm going out on a limb here and assuming the issues with
| their code don't begin and end with async.
| cercatrova wrote:
| > I've never seen someone who understands promises or
| async await do that. You'd have to know nothing about
| what the async keyword actually does to be compelled to
| do that.
|
| > usually junior front-end
|
| So you agree with each other then?
| eurasiantiger wrote:
| Good day, axe handle.
| garyrob wrote:
| I'm a newbie with respect to JS and especially promises
| and async/await, but I need to learn. If you could point
| me to some resource that does a really good job of
| explaining all this, I'd appreciate it very much. I
| expect that I wouldn't be the only one. What's something
| you'd recommend to a junior developer so that they
| wouldn't be one of the "many devs", as you put it, who do
| the wrong thing?
| magnio wrote:
| Not the parent, but I recommend understanding the event
| loop first: here [0] is a very good talk. Then, read the
| chapter on promise on javascript.info [1], as it explains
| the problems Promise set out to solve (callback hell),
| then as usual the excellent MDN article [2].
|
| [0] https://www.youtube.com/watch?v=8aGhZQkoFbQ
|
| [1] https://javascript.info/async
|
| [2] https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Guid...
| stareatgoats wrote:
| The whole async/await paradigm (including promises) is
| trying to fit square pegs into round holes (trying to
| make async processes look like they are synchronous).
| Just look at the comments on this page; even though
| async/await/promises have been around for years they are
| still causing difficulties for even the most experienced
| devs.
|
| It doesn't mean it can't be mastered, it can, but the
| whole paradigm is so fraught with pitfalls and conceptual
| difficulties that an codebase that uses it in any
| extensive way will forever be unstable. The whole thing
| is supposed to help against callback hell, but that can
| be better solved by a simple thenable object.
|
| Not a popular opinion I know, but there you go.
| doliveira wrote:
| > simple thenable object
|
| Wouldn't you simply be reinventing promises?
| eurasiantiger wrote:
| > trying to make async processes look like they are
| synchronous
|
| That is NOT what async/await is about -- after all, async
| explicitly marks functions as asynchronous. Not exactly
| trying to hide that.
|
| Async/await is syntactic sugar. It is there to make
| working with Promises less verbose.
|
| That is almost all: await pauses current function
| execution in the main event loop, which is an important
| detail
| lmc wrote:
| This doesn't directly answer your question, but...
| something to watch out for is experienced developers can
| also struggle with promises and async code if they've
| spent most of their career working with sync code. And
| when we 'get' it, the difficulty of the journey is often
| understated. This stuff can be _hard_ , so don't sweat it
| if it seems frustrating. (On the other hand, it may be
| easier if you don't have years of sync patterns to
| mentally set aside )
| savanaly wrote:
| This is absolutely correct why is it being downvoted lol
| Kwantuum wrote:
| It really isn't, most native/standard APIs do not return
| Promises. Lots of things use callbacks. Being comfortable
| creating Promises from a callback-based API is definitely
| something any competent JS dev ought to be able to do.
| MrJohz wrote:
| A lot of APIs produce promises these days. The big one
| that I always need to promisify is `setTimeout`, but
| apart from that, I tend to find that if I'm using the
| `new Promise` API, I'm usually doing something wrong.
|
| With Node APIs, there's a promisified version of pretty
| much everything. With browser APIs, there's usually a
| version with promises, and I'm struggling to think of an
| asynchronous API without promises that hasn't been
| superseded by something else (e.g. XMLHttpRequest ->
| fetch). If I'm converting from an event-emitter API to
| promises, there's usually going to be an impedance
| mismatch between the expectations of the event-based
| system and the promise-based system, and I probably need
| to explore another option.
|
| I agree that any competent JS dev should understand how
| to create promises "from scratch" like this. But it still
| should probably be a fairly rare occurrence, and if I see
| a lot of `new Promise` calls in one place, it's pretty
| much always a sign that someone doesn't really understand
| how promises work.
| lmc wrote:
| Here's a few. The docs link to some others that may also
| be useful...
|
| Promise.resolve()
|
| https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| You can use this in place of new Promise() (though you
| rarely need it, as an async function automatically wraps
| any non-promise return value in a promise.)
|
| ----------
|
| Promise.all()
|
| https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| For making requests in parallel, and returning when all
| have been successful.
|
| ----------
|
| Promise.allSettled()
|
| https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| For making requests in parallel, and returning when all
| have completed whether successful or not.
|
| ----------
|
| Promise.any()
|
| https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...
|
| For making requests in parallel, and returning the first
| successful response.
|
| --------
|
| As mentioned in sibling comment, things like fetch and DB
| calls return promises anyway, so the above are mostly
| only useful for working with multiple other promises.
| mananaysiempre wrote:
| > The point of async/await is not to _never_ have the word
| "Promise" appear in your code.
|
| Indeed it is not the point, and in that sense your criticism of
| the article is correct.
|
| However, there _are_ people who argue[1] that it is, in fact, a
| good thing to never have the word "Promise" appear in your
| code, that _not_ having the freedom to execute whatever code
| you want after launching an asynchronous operation and instead
| having the predictability of always returning to the scheduler
| makes reasoning about asynchronous code much simpler. (For one
| thing, you can now reliably think--and have syntactic support
| for thinking--in terms of sequential coroutines with
| predictable yield points and known parents.)
|
| In the context that Smith refers to (trio vs asyncio in Python,
| regarding which also see the first, more Python-specific but
| IMO better-argued post[2]) my experience actually bears that
| out. How well this works given the historical API baggage in
| JavaScript, I don't know, but probably not very well.
|
| Another curious thing is that the line of research JavaScript
| promises originate from[3] (Mark Miller co-wrote the spec
| proposal IIRC) does not use them as the user interface;
| instead, using the "eventual send"[4], you queue up method
| calls to objects (which can be promised results of other queued
| calls, and you may pass other promised objects as arguments).
|
| I cannot clearly articulate the relation of this to Smith's
| notion of "structured concurrency" (I wish I could), but in any
| case it contributes some more weight to the opinion that the
| ergonomics of Promises are indeed subpar. It might well be,
| though, that in the context of JavaScript you can't actually
| enforce enough structure on the preexisting APIs to build a
| superior alternative.
|
| [1] https://vorpus.org/blog/notes-on-structured-concurrency-
| or-g...
|
| [2] https://vorpus.org/blog/some-thoughts-on-asynchronous-api-
| de...
|
| [3] http://erights.org/talks/thesis/index.html
|
| [4] http://erights.org/elib/distrib/pipeline.html
| gotaquestion wrote:
| > A lot of the argument appears to be the author extrapolating
| from his own lack of familiarity to others
|
| This is literally 97.5% of all tech blogs, and a disappointing
| amount of content on HN front page.
|
| I feel bad for the author. Nothing like asserting your
| ignorance on a blog. There's a time and a place for literally
| every language construct.
| gompertz wrote:
| Agreed, and I think some authors need to be more humble. If a
| construct or design choice exists, its far more likely
| someone (or a team) spent hundreds more hours intentionally
| reasoning for its existence in the first place; over your
| passive duration of usage.
| BenoitEssiambre wrote:
| Async/await is certainly not promises. In fact it would be much
| better implemented without promises as I proposed here:
| https://es.discourse.group/t/callback-based-simplified-async...
|
| I would even say that async/await is anti-promise, it takes the
| main functionality of promises, a caching layer for results and
| errors that allows you to add the code continuation later and
| elsewhere (which is a major footgun imo) and coerces the
| execution flow back to going on the next line and provided
| immediately at compile time which results in a cleaner flow but
| not as clean, stateless, efficient or functional as if you were
| to remove the promises completely. Having an additional caching
| layer and state machine around every asynchronous function call
| is quite inefficient.
|
| The essence of async/await is not promises, it's the underlying
| javascript generator (https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...) functionality combined with
| asynchronous code to stop and start the generator. It's the
| ability to pause and resume function execution based on
| asynchronous operations.
|
| The promise functionality, the caching layer and state machine
| for results is basically sanitized away with async/await, it
| becomes dead-weight computation. The only benefit of promises
| in async/await code is being able to more easily interface with
| other promise laden code which you don't need once you have
| async/await and a library like
| https://www.npmjs.com/package/async for more complex cases.
|
| Note that promises based async/await is also a mess of an
| implementation that breaks stack traces and needs to support
| tons of odd statement corner cases (basically anything that can
| return an object that could be a promise) whereas a
| continuation passing style async/await would be a much simpler
| implementation that would only apply to function calls and
| maintain stack traces. We would get that stack trace support
| automatically because of the great work of whoever implemented
| javascript generators which seem to already carry stack traces
| across paused/resumed functions (if you don't wrap in
| promises).
| nameisname wrote:
| I don't agree with you fully. Async most certainly always
| returns a promise and as such can easily be combined with
| promise functions, like Promise.all. Your understanding of
| stack traces on promises is also old/uninformed. I work on an
| enterprise grade node app that was started back in node 0.11.
| Bluebird, which was what one used, offers good stack tracing
| and improved stack tracing has been available for async await
| for a few years now. Infact, I don't think I have had to go
| out of my way to get a decent stack trace in about a year or
| more now. There are certain paradigms you can adopt that
| keeps promise chains clean just like you would adopt with
| callbacks. I can't comment on if async await was implemented
| poorly as you are certainly more knowledgeable there with the
| RFC you shared.
| gorjusborg wrote:
| Exactly. Async/await just an alternative way to represent a
| promise chain.
|
| My feeling is that mixing the use of promise chains and
| async/await can make code hard to follow, so I ask
| colleages to not mix them in the same function.
|
| Sure, async/await is syntactic sugar for a generator that
| chains promises into a promise chain for you. Not sure why
| you'd care. That is an implementation detail that never
| gets exposed to the user.
|
| The only time I feel that promise chains are better
| async/await is when treating Promise as a monad
| https://blog.bitsrc.io/out-with-async-await-and-in-with-
| prom...
| [deleted]
| anonymousDan wrote:
| I don't find the first example convincing at all - to me await
| clearly indicates you are going to be blocking until the result
| arrives.
| bsuvc wrote:
| > I Avoid Async/Await
|
| Tell me you're a difficult teammate without saying you're a
| difficult teammate.
| forty wrote:
| I don't mind using async/await when I use promises, but in my
| experience people who never worked with bare promises have a hard
| time really understanding async/await and will eventually need
| help from people who did for harder problems. Just like people
| who never used callback flows often struggle to understand well
| asynchronicity and mess up with promises.
| emilfihlman wrote:
| Promises are the most awful programming syntax I have ever come
| across.
|
| Await is sweet release from death as nowadays almost all apis are
| forced onto promises. Thank god xhr and websockets came before
| promises were mainstream, but for example webserial is absolutely
| horrible to use.
|
| Function callbacks are the best, though I wish JS would support
| function scheduling.
| dimgl wrote:
| Who actually thinks like this?
|
| Callbacks were absolutely unmaintainable. Promises (and
| `async/await`) are really not that hard.
| beebeepka wrote:
| It's funny you say this because I have been promisifying xhr
| for years until fetch came along.
|
| And these days I do the same when I want to simplify WebWorker
| communication. Promises are great
| atsjie wrote:
| I never liked promises when they were first introduced because I
| never perceived this "callback hell" to be an unmanageable
| problem; there are excellent libraries to manage callback
| composition, e.g. https://caolan.github.io/async/v3/
|
| When Promises came along I didn't feel they added much value; in
| fact it made things more complicated (new paradigm, harder to
| compose).
|
| Still, I went with it because everyone else went with it.
|
| Then async/await came along, I had the same issues as described
| in the article. Most issues in the article I've made manageable
| (learning patterns over time, using libraries and/or
| conventions).
|
| To me the article reads as someone who worked with async/await
| for some time and never wanted to embrace and work with them in
| the first place.
|
| I'll be honest; if I'm working in a team with a collegue trying
| to force all code to use promises instead of async/await I'd
| probably ask him to stop doing that and escalate depending on the
| response.
|
| Being idiomatic is very important, it makes code recognizable for
| newer devs. Had I stuck with callbacks until now I'd be writing
| code few young JS/TS devs would understand or like to work on.
| jrochkind1 wrote:
| The semantics of the library you link to seems fairly
| similar/analogous to promises to me! I'd in fact call promises
| themselves "an excellent library to manage callback
| composition", I don't see any reason to prefer the one you link
| over the promises api -- even if neither were built-in to JS.
| (And promises of course originally were not).
| atsjie wrote:
| Your comment is a bit misleading, I doubt you looked for more
| than 30 seconds at that lib's documentation given the lack of
| detail in your response.
|
| Just to be clear; I would never use that lib in any
| circumstance right now, I'd always prefer async/await for
| idiomatic reasons.
|
| But please have a look at
| http://caolan.github.io/async/v3/docs.html#controlflow
|
| Forever, queue, series, times, retry, until, waterfall,
| whilst, etc etc... these are patterns that can all be
| achieved with promises too but I am sure most devs will do a
| google search before they do. In fact; some patterns are
| better served by a library.
|
| Only just yesterday I added
| https://www.npmjs.com/package/p-limit to get concurrent
| promise limitation behaviour; one of the many features of
| that async-lib as well.
|
| Don't misinterpret this as me suggesting these features
| should be added to the standard; far from it. Community libs
| do this job quite well.
|
| I moved on from async, I moved on from Promises and embraced
| async/await.
|
| Do I like it? No, I much rather use Golang's channels. But
| async/await is idiomatic and plenty of libs out there to
| handle complex use cases.
|
| Almost everyone has moved on, and anyone writing promises at
| this moment is just creating legacy for anyone who is going
| to maintain it. Is that a good reason in itself? No, but it
| is a very valid one, that is my point.
| [deleted]
| jseban wrote:
| Same here, I've never had that callback hell problem either, I
| think it's pretty elegant actually and you can do a lot of nice
| refactoring by extracting callbacks into variables. And the big
| strength is that your programming actually looks the way that
| the runtime works. It just becomes confusing with another
| abstraction that obfuscates the way the runtime works and
| requires you to now have this mental translation model.
| namelosw wrote:
| Async/await to Promise in JavaScript is just do notation to
| monads in Haskell, what's the problem?
| whatdidiexpect wrote:
| unityByFreedom wrote:
| I'll take "blog posts asserting silly things for clicks" for
| $100, Alex.
|
| All of this author's examples using .then() are single-line
| functions. Seems contrived to suit their opinion.
|
| Just use the right tool for the job.
| pawelmurias wrote:
| Just typical medium garbage with constructed examples to claim
| that their artificial view point isn't _that_ stupid.
| BigJono wrote:
| You pretty much just described half the TC-39 proposals. I
| _wish_ this shit was limited to noobs on Medium.
| einpoklum wrote:
| I like big promises and I cannot lie?
| robmccoll wrote:
| Everytime I use a language with async / await syntax, I find
| myself wishing that awaiting async functions was the default
| behavior and that I had to specify when I didn't want that
| behavior. So much boilerplate, so many times I've seen bugs that
| are the result of not being aware of a function being
| asynchronous or that the function was later made asynchronous.
| Most of the time you want seemingly synchronous behavior within a
| request because it's easier to read, write, understand, and
| reason about and concurrent progress across requests. The places
| where I can overlap I/O within a request can be manually
| specified. I guess this is kind of how Go works (although that's
| parallelism and concurrency).
| davidmurdoch wrote:
| I like this idea!
| chmod775 wrote:
| You might be interested in these linter rules:
|
| https://typescript-eslint.io/rules/no-misused-promises/
|
| https://typescript-eslint.io/rules/no-floating-promises/
|
| They can catch some common mistakes. Of course these checks can
| only work reliably when you're using a typed language.
|
| I'm still looking for one that forces me to put an await in
| front of every function call that returns a promise, unless I
| opt out.
| oaiey wrote:
| I actually start to appreciate function coloring in C# to never
| forget that I am operating on asynchronous code. In .NET the
| compiler also throws warnings if you do not await or pickup the
| task/promise.
|
| I also believe that you should see where asynchronous behavior
| can happen in your code. So from that angle i also appreciate
| the keyword and the coloring. Hidden resource access is pure
| evil.
| audunw wrote:
| You may want to check out Zig's async/await. Seems to be closer
| to what you're suggesting. It's one of the few implementations
| where functions don't have an "async"/"no-async" color, and
| where you use the same IO functions regardless of whether you
| want to use them asynchronously or synchronously (if I remember
| correctly).
| [deleted]
| whatdidiexpect2 wrote:
| rootusrootus wrote:
| Even though I feel competent enough using them, I continue to
| dislike promises in any form, no matter how much syntactic sugar
| there is.
| beebeepka wrote:
| I wonder what do you do instead.
| cube00 wrote:
| It's interesting using async/await, setting the target ES to be
| an older version and watching how the TypeScript compiler
| replaces async/await with yields and then no yields but a ton of
| scary looking boilerplate as you progress further back through
| older versions of ES.
| Aeolun wrote:
| Many awaits -> many, many case statements.
| vbezhenar wrote:
| Await basically splits function in two functions (before and
| after) and uses second function as a callback for promise then.
| Error handling makes it a little bit more complex, but not
| much. Babel seems to make it harder than it needs to be, but
| may be that makes sense from performance or maintainability
| perspective.
| todd3834 wrote:
| Very strange to compare async/await to promises when it is just a
| part of promises. There are rare situations where promise
| callbacks might be easier to read but those are more edge cases
| and a matter of personal preference.
| parentheses wrote:
| JavaScript Promises have their issues - the same ones all
| asynchrony-providing libraries do. I feel the author takes a hard
| stance on something quite nuanced.
|
| Syntactic sugar hides the API, making it more ergonomic, also
| leaking those problems. Same problem, different API
| eurasiantiger wrote:
| I stopped reading after the author made it clear they do not
| understand async/await is merely syntactic sugar for Promises.
| pelorat wrote:
| Depends on the language. In C# they most definately not only
| syntactic sugar.
| dimgl wrote:
| This article is on JavaScript.
| [deleted]
| tobyhinloopen wrote:
| I avoid Javascript. Not because Javascript sucks, but most
| libraries for Javascript suck.
| valcron1000 wrote:
| So "do notation considered harmful"?. Too bad that languages like
| Haskell are not very popular. These kind of discussions feel
| pointless.
| fithisux wrote:
| I use it in python and I love it.
| darepublic wrote:
| This take is garbage imo. Coming from someone who first used
| jquery deferred, then native promises and now async await. If you
| are too inexperienced to use await properly no reason to think
| callback hell is going to make it better
| mojuba wrote:
| I know the article is about JavaScript, but I was wondering how
| Swift resolves the problem of weak self within the async/await
| model.
|
| If the part of the function after await _is_ the callback, how do
| I specify weak self if I need to?
| ninkendo wrote:
| You can generally do something like: Task {
| [weak self] in await foo() self?.bar()
| // self may be nil }
|
| That is, you can just make a new task which awaits on something
| and then does more work, and that Task can be tagged with weak
| self so that self can be freed.
| mojuba wrote:
| So basically no way to do the same with await, other than of
| course declaring a weak local var.
| ninkendo wrote:
| If you're await'ing a variable directly in the same scope,
| of course self will be retained, you're still running
| inside self's scope.
|
| I guess I'm confused by the question... if you want to
| await something and allow self to be freed in the process,
| you can use Task{ [weak self] } and then return early. If
| you don't want to return early, you can't really release
| self yet, since the scope shouldn't outlive self.
| Go0the0gophers wrote:
| Sure async/await makes your casde more complicated . That reason
| why, I prefer use Go for I/O wait application because I don't
| need to manage this Go doesn't it fore me. My code still readable
| and simple.
| vips7L wrote:
| Until you need to do any error handling.
| codecurve wrote:
| > Give better cues that we are in an asynchronous mental model
|
| The `async` keyword(!) is objectively a clearer signal that the
| code in question is asynchronous. That's why type-checkers use it
| to prevent you from doing dumb stuff like awaiting in a
| synchronous function.
|
| > In simple cases express the code at least as cleanly as
| async/await
|
| It's pretty hard to see much of an argument here. How can the
| promise version ever be seen as "at least as clean"?
| await someTask() // vs someTask().then(() =>
| ...);
|
| Even beyond the syntax here, the promise forces you into at least
| one level deep of nesting, which instantly makes it much trickier
| to manage intermediate variables, which either need to be passed
| along as part of the result (usually resulting in an unwieldy
| blob of data) or the promises need to be nested (pyramid of doom)
| so that inner callbacks can access the results from outer
| promises.
|
| > Provides a much cleaner option for more complex workflows that
| include error handling and parallelisation.
|
| If you've somehow come to the conclusion that `Promise.all`
| doesn't work with `async/await` then you have probably
| misunderstood the relationship between `async` functions and
| promises. They're the same thing. Want to parallelise a bunch of
| `await` statements? You can still use `Promise.all`!
|
| I do occasionally find try-catch to be awkward, but that's
| because it creates a new lexical scope (just like promise
| callbacks do). I also think the consistency from having one
| unified way to handle errors in sync/async contexts justifies it.
| zebracanevra wrote:
| If you're doing nothing in between your async calls, using
| .then/.catch might be simpler.
|
| As soon as you need to introduce local variables and complex
| control structures, not having shared closures between your .then
| methods becomes extremely limiting. Hence, the async/await sugar.
|
| About having to add await to ensure your error is handled with
| the try catch -- _not_ putting an await before a promise is
| something I use all the time. Being able to store tasks /promises
| and reuse/await them later is a clear advantage to other types of
| async code (such as coroutines).
| Aeolun wrote:
| > Being able to store tasks/promises and reuse/await them later
| is a clear advantage
|
| This sounds like horrible spaghetti. I can understand you might
| need to do it in exceptional circumstances, but I wouldn't make
| a habit of it.
| zebracanevra wrote:
| 99% of the time you'll be storing promises in order to await
| on them in the same async method.
|
| Those other 1% of times make for quite intuitive solutions, I
| think. For example, a cache. Most caches are request =>
| already finished response, but with a lookup of promises you
| can easily do request => _in-progress_ responses, for de-
| deplication.
| yourad_io wrote:
| Graceful shutdown & startup are two use cases that I used
| this for, just today.
|
| When my express server gets a SIGTERM I want it to stop
| accepting new requests but finish pending requests before
| exiting.
|
| Likewise during startup, the HTTP server is currently up
| before all resources are available - DB backends and the
| like. So I await a .ready promise before all requests iff we
| are not inited.
|
| It can be abused into spaghetti for sure, but actual use
| cases are not that exotic imho
| madeofpalk wrote:
| One thing I really like async/await for is you can use them in
| for-loops. It makes it significantly easier to do async work in
| what was previously very synchronous.
| ElFitz wrote:
| I thought await in loops was a big no no, unless it has
| changed since I last checked?
|
| Doesn't it remove any opportunity for parallelism, making the
| loop take much longer than it would with a Promise.all?
|
| Or am I missing something?
| madeofpalk wrote:
| You're right(ish) - the async functions would not run in
| parallel. But sometimes that's either fine, or I don't want
| them to run in parallel.
| yourad_io wrote:
| Sometimes you want to do things sequentially, so for..of x
| { await } is the way to go
|
| If you want parallelism: await Promise.all(x.map(...))
| jonathanlydall wrote:
| Completely my feelings too. He doesn't actually address this,
| and it is actually the really painful part about promises which
| async/await makes infinitely better.
|
| I have had cases where converting .then(...) code to
| async/await made the code infinitely easier to
| understand/reason about and made it trivial remove bugs which
| were present due to the complexities of dealing with control
| flow logic.
|
| It strikes me that the author is just already "used to"
| promises and as such is trying to justify their preference for
| them with examples which they believe "prove" that promises are
| "better", but actually fail to do so. For every single one of
| their examples, async/await is no worse, if not better.
|
| For example, their argument about inadvertently serialized work
| with the the two save calls. They are claiming that ".then()"
| is more obviously serializing than the "await" keyword, which
| is a highly subjective claim. If there is a problem here, it's
| that some developers don't understand/know about all the
| asynchronous tools which are available and so are unaware of
| the option of using Promises.all(...).
|
| Another argument for async/await compared to promises is the
| following: try { await
| doSomething(); } catch() { // Do a
| particular error handling for doSomething() failing }
| try { await doSomethingElse(); }
| catch() { // Do a different particular error
| handling for doSomethingElse() failing }
|
| What's the best way to reproduce this in promises only land,
| for example, does the following work?
| doSomething() .catch(() => { // Do a
| particular error handling for doSomething() failing })
| .then(() => doSomethingElse()) .catch(() => {
| // Do a different particular error handling for
| doSomethingElse() failing });
|
| I _think_ it works, but I honestly don 't know for sure offhand
| and to be sure I would need to check the documentation for
| promises, whereas for the async/await approach, there is no
| question. And if the above _doesn 't_ work, then I would have
| to call the .then() from inside the first .catch() block, which
| is awful code to read and interpret.
| alerighi wrote:
| It's the same code. Async/await is just syntactic sugar over
| the promise syntax.
|
| To me depends on what you are doing, there are cases where
| using .then()/.catch() and .finnally() makes the code more
| concise and simpler than using async/await.
| beardedetim wrote:
| .catch() returns a promise so you can call the then method on
| it again, yes. If you throw from either catch or then it will
| trigger the next catch method.
| too_root wrote:
| I agree. I think the article takes a simplistic view of the use
| cases in an effort to make the post consumable but in doing so,
| misses some of the point behind the syntax.
| BillyTheKing wrote:
| the whole blog-post seems a little constructed tbh - If I look at
| await doSomething(param) await doSomethingElse(param)
|
| the very first thing that comes to mind is that this can be very,
| very easily optimised by just doing await Promise.all([
| doSomething(param), doSomethingElse(param) ])
|
| This is something I come across literally every day - why exactly
| would that be an argument against async/await? much so on the
| contrary, I find const myResult = await Promise.all([])
| significantly more expressive, than
| Promise.all([]).then(doSomething)
|
| Also, as others have already pointed out - async/await is just
| syntactic sugar around promises and can be used in addition to
| 'normal' promise syntax (as shown above)
| johnny22 wrote:
| Once you're sure the operation doesn't need to happen serially
| I hope :)
| biglost wrote:
| Maybe im wrong but i think await is not for everything it's
| another tool, we as developer must known these tools, and when
| its better to use it or no
| jdrc wrote:
| We need an AI to design a language that is optimal for humans to
| program with. Languages should be designed by behavioral
| scientists rather that computer scientists bcause their goal is
| to adapt to the human rather than the machine. For example one of
| the biggest limitations of humans is limited capacity of working
| memory. Async paradigms exhaust this limit very early and lead to
| frustration compared to serial programming. Writing one of them
| in the form of the other doesn't solve any problem and is indeed
| a travesty
| manigandham wrote:
| All async/await is built on promises/futures/tasks, which
| themselves are built on yield/generators. Most modern languages
| have converged on this and they are not different paradigms but
| rather just newer syntax.
|
| Async/await is better for expressing concurrent logic more
| tersely and in the common "sync" format, and failure to
| understand what it means is just that, a failure in
| understanding.
|
| There's no convincing argument here to use "promises instead
| async" because it's the same thing.
| STRML wrote:
| > a try block in JavaScript immediately opts that section of code
| out of many engine optimizations as the code can no longer be
| broken down into determinative pieces
|
| This isn't true and the dependent clause doesn't even make sense.
|
| Yes, V8 had trouble optimizing try/catch back in the Crankshaft
| days. But those days are _long_ gone, and major engines (V8
| /TurboFan, JSC, and Mozilla's latest *Monkey) handle this
| construct quite well. Even if this were not true, it would be
| meaningless, as this is simply syntactic sugar over
| `Promise#catch()`, which you would be using anyway.
| zazibar wrote:
| _Now maybe it's just my familiarity with Promises, but I look at
| the third example and I can quickly see an opportunity._
|
| This entire article is built around the author's ignorance and
| could easily be summarised as "I avoid async/await syntax because
| I'm more familiar with promises". The author doesn't even appear
| to understand that async/await is syntactic sugar for promises.
| codeflo wrote:
| The author even implies in a footnote that switch statements
| are unusable. I mean, we probably all had painful experience
| with the "gotcha", and I appreciate efforts towards safer
| designs. But I mean, let's not be ridiculous. It works fine.
| gitfan86 wrote:
| There is a growing trend where people just memorize how to do
| things instead of understanding what those things actually do.
| sidewndr46 wrote:
| I did not take me long to reach the same conclusion. The
| article can be summarized as "I am ignorant of the meaning of
| async/await, thus I don't use it". This is perhaps one
| incremental improvement from "I am ignorant of async/await,
| thus I use it poorly". But in the wrong direction.
| imtringued wrote:
| What the author wants is something like this:
| async { save() save() } catch
| (Exception e) { console.log("Handle error")
| }
|
| async does not deliver this at all.
| _ZeD_ wrote:
| what the author wants doesn't exists because the two saves
| will not actually run in parallel in any case. Not with
| `async save(); async save();` nor with `Promise.all` nor with
| any callback or any other means.
|
| the author is conflating parallel with concurrent
| programming.
|
| and in (the mono-thread world of) javascript the two calls
| will still occurs sequentially.
| jjnoakes wrote:
| This is only partly true, but misses the point.
|
| Consider that 'save()' might do multiple steps under the
| covers (network, database, localstorage, whatever).
| Allowing those steps to run interleaved, if necessary (with
| Promise.all), might be quite different from serializing the
| 'save()' calls completely in the caller.
|
| So while it is true that neither is truly parallel in the
| "parallel vs concurrent" sense, it is not true that the
| "sequential"/"concurrent" execution of both styles has the
| same performance characteristics.
| jrwr wrote:
| I avoid Javascript outright because async/await/promise is
| confusing to me. I blame it on being a PHP Programmer and likes
| things to run serially.
| bdcravens wrote:
| It drove me crazy too, until I needed to use Puppeteer which
| requires you to write async/await (there are Puppeteer
| implementations in other languages, but they all seem to make
| compromises I didn't want). Generally speaking, async/await
| allows you to write code that looks and feels serial. Perhaps
| try using one of the async libraries for PHP to wrap your
| mind around the concept of async/await (like
| https://github.com/spatie/async)
| gorjusborg wrote:
| I felt the same way coming from a threaded language.
|
| Learning the event loop, then promises, then async/await is a
| must. Today, you probably should throw typescript on top.
|
| A steep learning curve just to get back to a typed language
| that can do things concurrently.
|
| You do get used to it, but it is a mess of stuff.
| rwalle wrote:
| This is ridiculous. Handling real threads is much more
| complicated than handling async calls and the event loop of
| JavaScript.
| AA-BA-94-2A-56 wrote:
| /r/gatekeeping
| wonnage wrote:
| Threads are their own steep learning curve, I think it's
| just hard to do two things at once.
| PaulDavisThe1st wrote:
| It's easy to do two things at once when you can ask two
| different entities to do them for you (threads).
|
| What's hard is thinking about how to coordinate the work
| they are doing for you: when to consider them done, how
| to ask them if they did the work successfully, what to do
| if they need to use the same tool at some point during
| the work etc.
| farmin wrote:
| Hyperscript can help with this. https://hyperscript.org/
|
| Makes using a bit of JavaScript relatively simple, just not
| much in Stack Exchange yet which means reading docs..
| snak wrote:
| Agreed, I stopped reading after this sentence.
| saimiam wrote:
| I didn't read the article like that at all.
|
| How would you handle two asynchronous saves which can happen in
| parallel without using a Promise.all? Don't think you can...and
| that's pretty much the entire point of the article.
|
| Async/await is useless unless you are willing to serialize your
| calls defeating the entire point of async code.
| jffry wrote:
| > How would you handle two asynchronous saves which can
| happen in parallel without using a Promise.all?
|
| This question doesn't make sense. Async/await is just a nicer
| syntax for interacting with promises. So my answer to your
| "gotcha" question is just: await
| Promise.all([..., ...])
|
| There's nothing impure going on here. The majority of the
| time, async/await can make it much easier to see a code's
| control flow by getting rid of most of the Promise related
| cruft and callbacks.
|
| I would call Promise.all a benefit here, as it makes it stand
| out where I'm doing something in parallel.
| rwalle wrote:
| Wait, why _don 't_ do that? What's the point?
| bilater wrote:
| Yup - I prefer async/await but that was actually a good
| example on optimizing multiple promises I had not though of
| before.
| jseban wrote:
| Promises are also just syntactic sugar to make your code look
| more synchronous, you can do everything with plain callbacks.
| Which I find ironic with the article that he argues against
| that but still just stops at the next turtle, instead of
| following his own advice and actually learning how Javascript
| and it's runtime works.
| igemnace wrote:
| Just because you use async/await doesn't mean you can't use
| Promise.all.
|
| In fact, my immediate intuition with the await examples _was_
| to parallelize with Promise.all. await
| Promise.all([/* build promises */]);
| IshKebab wrote:
| Yeah they had that in the post.
| RedShift1 wrote:
| const x = somethingAsync(); const y =
| somethingAsyncToo(); return { foo: await x, bar:
| await y }
|
| There is no point in returning one before the other because
| you need both?
| aGHz wrote:
| But in JavaScript, these two awaits will not happen in
| parallel, you really need to await Promise.all() for that.
| [deleted]
| tehbeard wrote:
| You've really missed the point spectacularly of that
| example.
|
| Both those promises start, and both are waited for after
| both have started..
|
| That is the same as promise.all... There's just an
| explicit order for the wait, rather than as they resolve,
| but the result is the same.
|
| Now, promise.any.... You'd have a point...
| aGHz wrote:
| This is correct, I wasn't paying attention.
| linkdd wrote:
| In Javascript, a Promise is started as soon as it is
| created. In other words, this is not the `await` that
| starts the Promise.
|
| If the first await is the slowest, the second one will
| return immediately (like calling .then on an already
| resolved promise).
| matt-attack wrote:
| Pretty sure there can be unexpected behavior if you wait
| too long before you do those awaits at the end.
| RedShift1 wrote:
| No, why would there be?
| matt-attack wrote:
| Node 16 will exit if an exception is thrown and not
| awaited for some finite period of time. So if your goal
| is to keep those promises in some cache and then resolve
| them later on at your leisure, you will find the entire
| node process will abort. There is a feature flag to
| restore the older behavior but it's a pretty big gotcha.
| RedShift1 wrote:
| There is no such finite period of time. You can call an
| async function and never await it.
|
| Exception handling is something completely different.
| Yes, if you call an async function and do not catch the
| exception, Node will stop. But that is independent of
| having called await or not. Whether or not you await
| something async does not affect exception behavior.
| codethief wrote:
| +1 Notably, this is different in Python where promises
| (futures) are executed lazily, i.e. when you await them.
| saimiam wrote:
| I think you're trying to recreate the semantics of
| Promise.all without using Promise.all.
|
| You're effectively saying that Promises are a better async
| programming paradigm than async/await...which is also what
| the author is saying in the article.
| RedShift1 wrote:
| I'm not saying anything about promises vs async/await.
| The original comment said that you can't have 2 async
| things happen in parallel without Promise.all, my code
| snippet proves that you can.
| divan wrote:
| Author mentions that async/await messes up with mental model of
| the code, and I think that's the most important issue with it,
| but it goes even deeper than described in the article.
|
| There are no inherently async or sync functions. It's not a
| property of a function, rather the property of what caller does
| after calling a function.
|
| Is throwing a ball an async or sync action? Well, if tennis robot
| machine spits one ball after another and doesn't care/wait about
| feedback - then it's async. If the tennis player hits the ball
| with a racket and puts all her/his attention into
| waiting/validating the result (essentially blocking) - then it's
| synchronous function. It's essentially an "attention" of the
| caller that defines sync or async.
|
| Marking code as inherently "sync" or "async" or claiming that one
| functions are "hard" and others are "easy" is the single dumbest
| idea I've seen in computer programming. And it's insane how
| contagious it is - seems like languages are more often
| copypasting features from other, instead of designing from the
| first principles.
| jffry wrote:
| Since this article is specifically about Javascript: I think
| the implementation of async/await in the language is heavily
| constrained by a desire for backwards compatibility.
|
| After all, async/await is ultimately promises all the way down,
| so you can happily write code and let a compiler turn it into
| ES5 that runs anywhere.
|
| If instead JS allowed you to shunt arbitrary function calls off
| into their own threads, then you would need bigger changes to
| the engines to support that, and you couldn't back-port that to
| vanilla javascript.
| divan wrote:
| To be honest, I find promises even worse concept for
| concurrent programming. At least I never think about things
| and behaviour in terms of "objects that will yield value in
| the future". To use promises I need to build a layer of
| conversion between "how my brain thinks about world" and
| "passing around promises" - and that's just textbook
| accidental complexity and unneeded cognitive load.
|
| The least cognitively expensive model for concurrent
| programming is CSP, precisely because it fits into how we
| humans reason about world.
| jseban wrote:
| Me too, there is no concurrent programming in Javascript.
| It's a continuation passing style language and a single
| threaded runtime, I wish people would just spend a little
| time learning how that works and normal javascript will
| make a lot of sense and not look "yucky" anymore. And you
| will realise how great of a fit that is for UI programming
| and reacting to events from the user, where you don't have
| to think about blocking the UI thread like you need to in
| Java.
| timcavel wrote:
| This confuses async with parallelism, probably because JavaScript
| is his threading model.
| codeflo wrote:
| Some language features aren't aimed at toy examples, and if
| that's the scope of your thinking, you won't see the point.
|
| For example, I like how the catch block is just a single function
| call to handleErrorSomehow -- which would be totally fine for an
| example, if it weren't for the fact that the author makes a
| special note of how convenient it is that the Promise variant
| reduces to just .catch(handleErrorSomehow). Suuuper-realistic.
|
| I should probably admit that I also didn't completely see the
| point of async/await -- this was years ago in C# -- until I first
| had a chance to use it inside a complex loop of some kind. I
| think it's a good exercise to try manually desugaring such an
| example. It really makes you appreciate what the compiler's doing
| for you in these cases.
| jokethrowaway wrote:
| Async / await is nice syntactic sugar. I don't understand why
| they copied the naming from c# over do syntax in Haskell (which
| dates roughly 1995, if I'm not mistaken).
|
| The main problem is error handling, having to use try / catch is
| pure cancer and really screw up your closure. You can use catch
| together with await / async but then you're not dealing with
| exceptions outside the promises.
| frankincense wrote:
| Typescript and C# are both developed and maintained by
| Microsoft, and the lead architect of C# is also the creator of
| Typescript. (Both great languages, IMO)
| valenterry wrote:
| async/await is not the same as the do-notation, that's why.
| xigoi wrote:
| How is it not?
| valenterry wrote:
| Many reasons, for example:
|
| 1. do notation works and is used for any monadic type (e.g.
| lists, parsers, promises, resources like database-
| connection-contexts, ...) while async/await works for
| promises only
|
| 2. async works on the function-level and only there while
| do-notation can be used in any place where a normal
| expression can be used, including being abitrarily nested.
|
| There are more differences, but that should be enough food
| for the mind to think about it.
|
| You could summarize it as: async/await makes it easier to
| work with promises on the syntax level and trying to make
| async code look like sync code, while the do-notation does
| not try to make async code to look like sync code, but
| rather embrace that sometimes two different semantical
| flows are interwined (one inside a monadic context, one
| pure) and makes it easier to work with them on the syntax
| level while at the same time making the two look
| _different_ explicitly.
| xigoi wrote:
| I don't think we disagree. JavaScript should've gone with
| do notation precisely because it's a generalization of
| async/await.
| valenterry wrote:
| From the perspective of someone wanting to use the
| powerful do-notation, sure. But at the same time, the
| hurdle for many developers who just want to "use promises
| with ease", it would have been a steeper learning curve
| with much less beginner-friendly syntax.
|
| do-notation doesn't really help to deal with promises, it
| even makes it harder because it is confusing at the
| beginning. Async/await makes using promises easier and
| hides problems for some time at least. I can see why they
| chose to use async/await.
|
| Just to give an example: error handling with try/catch.
| That doesn't work with do-notation, but with async/await
| you can integrate it (more or less at least) and it looks
| like sync code.
| gombosg wrote:
| As a fullstack dev, I worked with
|
| - Spring MVC (Java Futures)
|
| - Spring Webflux (Reactor observables)
|
| - Scala (Scala Futures & for comprehensions)
|
| - TS async/await
|
| - Angular observables
|
| - React hooks (which combined with Redux, React Query etc. is a
| special approach to async programming.)
|
| I think it's very important to understand that ultimately, async
| (non-blocking) programming is kind of a "hard problem" and there
| is no trivial solution for it. But we can't get around it because
| we must not block code execution: Javascript runtime environments
| are single-threaded (except workers) and the JVM has limited
| thread count, too.
|
| Any senior developer has to understand how each of these
| approaches work and how they are trying to solve the issue of
| non-blocking code execution. I dare say that this problem area is
| one of the hardest ones during becoming a more senior developer.
|
| In terms of ergonomy (developer experience) when it comes to
| simple chained non-blocking calls, in my opinion nothing beats
| async/await in terms of syntax simplicity. I miss them from Java
| & Scala! No indentation, lambda functions, flatmaps, monads or
| special methods/hooks needed. You just have the result of an
| async method right there.
|
| I don't agree with the author that we shouldn't use async/await
| because it _looks like_ synchronous code. We _should_ use it to
| keep syntax simple and clean wherever possible!
|
| The developer has to properly learn the language fundamentals
| (and how JS code execution works) and know that async/await is
| just syntax sugar over Promises and generator functions. If they
| don't do this, then having a Promise.then() chain instead of
| async/await won't help that developer write better code.
|
| On the other hand, once they understand it, they can freely
| choose between async/await and Promises depending on what needs
| to be written.
| mpweiher wrote:
| > I don't agree with the author that we shouldn't use
| async/await because it _looks like_ synchronous code.
|
| Not having code that is actively deceptive sounds like a good
| idea to me, so agree with the author.
|
| > We _should_ use it to keep syntax simple and clean wherever
| possible!
|
| Having clean and simple syntax for async operations is a
| laudable goal.
|
| Having code that is actively deceptive about its semantics
| seems less than ideal for accomplishing this goal.
|
| Maybe we need to figure out new and different syntactic
| constructs that are clean and simple, but do not pretend to be
| something they are not?
| wonnage wrote:
| How are they deceptive? I don't know where the idea that
| these keywords somehow mimic synchronous code comes from.
| mpweiher wrote:
| It's not the keywords that are deceptive. It's keeping the
| rest of the code in a structure that is extremely different
| from what actually happens.
|
| https://dl.acm.org/doi/10.1145/3297280.3297528
|
| "To solve JavaScript's callback hell problem, several
| language mechanisms like Promise and async/await have
| already been introduced to JavaScript. Using async/await,
| which is the most promising one, callback hell code can be
| rewritten to another simple and shallow nested code with
| (almost) the same behavior. Unfortunately, however, it is
| still difficult to precisely understand the execution order
| of the rewritten async/await code, because the semantics of
| async/await is difficult.
|
| This paper first clarifies that this problem is caused by
| the difficulty of the async/await semantics. Then, we
| propose and implement a novel async/await visualizer called
| AwaitViz, to support for programmers to understand the
| execution order of async/await. Our contribution is
| twofold. First, we show the feasibility of implementing the
| visualizer AwaitViz based on source-code instrumentation,
| which provides precise information on the JavaScript's
| asynchronous behavior. Second, we show the difficulties and
| limitations of implementing AwaitViz."
| likeclockwork wrote:
| You can't talk about async await without talking about try,
| catch, and finally. I don't think async+await+try+catch+finally
| is simpler than promises in any way.
|
| I don't find anything simple or clean about imperative error
| handling.
| tm-guimaraes wrote:
| Try ou kotlin coroutines. The suspend keyword really helps out
| reasoning.
| weinzierl wrote:
| _" [..] and the JVM has limited thread count, too"_
|
| I think I understand you point when it comes to the essentially
| single threaded Javascript runtime, but the thread limit of the
| JVM is huge, so how does that matter?
|
| Not trying to be pedantic, just wanted to know if I missed
| something.
| gombosg wrote:
| Yes, for JVM, it's not that trivial and non-blocking code is
| definitely not mandatory. Our app backend has been written in
| a blocking fashion and we are gradually transitioning to non-
| blocking code.
|
| Your question is very valid as the JVM can handle even
| thousands of threads (given the host OS can).
|
| Having less threads does keep memory usage lower (as each
| thread in the thread pool has its own stack space). Also,
| it's more scalable because if using blocking code execution,
| you'll need to have one thread per concurrent request served.
| Lastly, thread starvation can become a problem with blocking
| code in case of delayed I/O or other failures. If that
| happens, the server becomes unable to serve new requests.
|
| See this SO answer: https://stackoverflow.com/a/63490797
| imbnwa wrote:
| Async/Await covers the 80% of use cases for async logic in JS.
| Most people aren't really using promises as multicast references.
| They don't call `then` in one place, hang on to the promise
| reference, then call `then` again somewhere else (perhaps to
| represent a cached value); they call `then` once on the reference
| because it's just a moment in a composite operation.
|
| It's for this reason that I think this library[0] is the more
| appropriate abstraction for that same 80% of use-cases, as its
| more memory efficient since you can represent the same composite
| operation that generates multiple promise references with a
| single object (a unicast reference instead). I haven't learned
| Rust but apparently the author bases this on Rust's ownership
| principle.
|
| [0]https://github.com/mitranim/posterus
| AshleysBrain wrote:
| Their promise-based error handling seems to skip over a major
| gotcha: if the first `save` call throws an exception, it does not
| get handled in the `.catch()` callback! It would require its own
| try-catch to handle. That is an overlooked benefit of try-catch
| in async functions: it handles everything, both normal exceptions
| and promise rejections.
| Izkata wrote:
| A rejected promise is passed along until it hits a catch. Both
| save calls are handled by the one there.
| LAC-Tech wrote:
| I kind of agree. I learned promises from the promise API -
| flatmapping over things, that will happen when the thing is
| resolved.
|
| I remember async/await was harder to grok because it seemed so
| arbitrary. A bunch of syntax sugar, and function decorations..
| promises were just method calls.
| omgbear wrote:
| For me, the key to async/await is a linter that complains about
| un-handled promises. I appreciate in Go that any green threads
| are created explicitly with `go`, whereas in typescript, they are
| signaled by the return type.
|
| My most recent background was in Go and so my mental model now is
| that async functions are similar to goroutines and `await` is a
| nice result collection mechanism.
|
| What's recently confused me is that exceptions from yet-to-be-
| awaited promises crash if you await anything else first.
| vhakulinen wrote:
| Regarding the error handling case, I've played around with the
| idea of implementing a Result type with typescript and hiding the
| errors behind that. The end result looks something like this
| const res = await getJSON('/thingy'); if
| (isErr(res)) { // Handle error. return;
| }
|
| You can read the details from here:
| https://thingsthatkeepmeupatnight.dev/posts/simple-typescrip...
| wereHamster wrote:
| Congrats, you've re-invented sumtypes!
| vhakulinen wrote:
| Rather, copied over from other languages.
___________________________________________________________________
(page generated 2022-04-16 23:02 UTC)