[HN Gopher] I avoid async/await in JavaScript
___________________________________________________________________
I avoid async/await in JavaScript
Author : fanf2
Score : 17 points
Date : 2024-08-13 17:42 UTC (5 hours ago)
(HTM) web link (uniqname.medium.com)
(TXT) w3m dump (uniqname.medium.com)
| dave4420 wrote:
| I wish we had syntax like concurrently {
| const user = await getUser(userId); const session =
| await getSession(sessionId); }
|
| meaning that
|
| - the getUser and getSession calls occur concurrently (it
| desugars to Promise.all)
|
| - user and session are available for use after the block
|
| - user and session appear directly next to the calls that give
| them their values, unlike with Promise.all
| odyssey7 wrote:
| It could get bloated rather quickly try {
| concurrently { const user = await getUser(userId);
| const session = await getSession(sessionId); } }
| catch (err) { ... }
| dave4420 wrote:
| I'm not attached to any particular syntax, I just want
| something with better ergonomics than using Promise.all
| directly.
| Wazako wrote:
| What's wrong with making Promise.all ?
|
| try { const [user, session] = await
| Promise.all([ getUser(userId),
| getSession(sessionId) ])
|
| } catch (err) { ... }
| mrozbarry wrote:
| My biggest gripe with async await is when it's used by people
| that don't understand promises. They interpret await as "let
| promise run" and not "this blocks the flow." I had someone show
| me code where they wanted to preload a bunch of images, and
| effectively had a for loop and did an await on each image in it,
| effectively running it as slow as possible. I showed him `await
| Promise.all(imageLoadingPromises)`, and he was confused why we
| needed `Promise.all` here.
|
| I also don't like how async/await ends up taking over a whole
| codebase. Often making one function async will cause you to
| update other functions to be async so you can do proper awaiting.
| We literally exchanged callback hell to async hell, and said it
| was better, but I'm not strictly convinced of this.
| lofenfew wrote:
| >We literally exchanged callback hell to async hell, and said
| it was better, but I'm not strictly convinced of this.
|
| It's obviously better. It's a bit unfortunate how much the
| history of the design decision shines through in the current
| solution, but it's unequivocally an improvement.
|
| I would prefer an approach where calls to "async" functions are
| implicitly awaited unless a keyword turns then into a promise,
| and all functions are implictly treated as async as needed,
| unless a keyword specifies that they return a promise, which
| should be awaited instead. This would make the majority case
| clearer, and force you to make the minority case explicit where
| it's currently implicit.
|
| I don't think this would help your coworkers who don't
| understand promises very much though.
| tyleo wrote:
| Chapter 15 of the 2007 release of "Programming Languages:
| Application and Interpretation" gives a really good
| motivation for the async/await syntax. Here is a link: https:
| //cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-0...
|
| > I would prefer an approach where calls to "async" functions
| are implicitly awaited unless a keyword turns then into a
| promise, and all functions are implictly treated as async as
| needed, unless a keyword specifies that they return a
| promise, which should be awaited instead. This would make the
| majority case clearer, and force you to make the minority
| case explicit where it's currently implicit.
|
| This is a very interesting idea and feels good in an initial
| 'gut check' sense.
| shivawu wrote:
| This is I believe how ziglang works on its asynchronous
| story
| mrozbarry wrote:
| I prefer the raw promise syntax. It makes the promise chain
| look more like what it is, an asynchronous pipeline, and you
| don't need to litter async everywhere.
| const myAsyncThing = async (init) => { let value =
| await task1(init); value = await task2(value);
| return value; } const myPromiseThing =
| (init) => task1(init).then(task2)
|
| I know this is a relatively simple and contrived example, but
| there are some times where async/await very much is bulky and
| gets in the way of just writing code.
| saurik wrote:
| I mean, you didn't even try :/. const
| myAsyncThing = async (init) => await task2(await
| task1(init))
|
| This makes the promise chain look much more like what it
| is, given that the rest of the language uses nesting calls
| to indicate "after".
| mrozbarry wrote:
| I didn't write it like that because it obscures the call
| order. I wasn't trying to compare code line/count, just
| flow control and readability, which is certainly
| subjective.
| THBC wrote:
| const value1 = await task1(init); const value2 =
| await task2(value1);
| THBC wrote:
| That's not "raw Promise" syntax, that's Thenable syntax.
|
| Compare with const myPromise = new
| Promise((resolve, reject) => resolve("foo"));
|
| Afterwards either try-catch await myPromise, or use
| myPromise.then().catch()
| moralestapia wrote:
| Can you give a concrete example of "async hell"?
| craftkiller wrote:
| > They interpret await as "let promise run"
|
| This is actually how rust handles async/await. If nothing is
| polling the future then the future is not running. If you want
| background execution like javascript then you need to hand it
| off to an executor (for example via `tokio::spawn`). Its
| actually pretty nice because then you can build a future and
| pass it around but you can decide if/when you want to execute
| it later.
|
| That being said, you still wouldn't want to do a for loop
| awaiting each future.
| throwitaway1123 wrote:
| > I also don't like how async/await ends up taking over a whole
| codebase. Often making one function async will cause you to
| update other functions to be async so you can do proper
| awaiting.
|
| This is basically the function coloring problem. There was some
| ardent discussion about this a few weeks ago on HN [1], where
| the top comment positioned function coloring as a feature
| because it highlights where expensive operations like IO might
| potentially be happening in your application. I'm not settled
| on the issue yet, but I saw a comment recently where someone
| pointed out an old API in Java where checking equality on two
| URLs makes a DNS request, and so something as simple as putting
| a URL in a hash table could end up involving a network request
| [2]. So maybe it is reasonable to color functions that connect
| to the network at the very least. If I used a URL comparison
| library and it returned a promise it would immediately set off
| alarms in my mind.
|
| [1] https://news.ycombinator.com/item?id=41001951
|
| [2] https://news.ycombinator.com/item?id=41143458
| spankalee wrote:
| It's not async/await that takes over a codebase, it's the async
| nature of functions period, no matter if you're using
| async/await, Promises, or callbacks.
|
| Callbacks still have the function coloring problem: If you
| write a function which calls another function with an on-
| complete callback argument, then your function (usually) has to
| also take an on-complete callback in order to represent
| completion.
|
| async/await makes this nicer, but it doesn't eliminate the
| underlying need to thread async completion all the way up your
| call stack.
| odyssey7 wrote:
| > It's just a tiny hint to get you thinking about what
| functionally style JavaScript could look like if we wanted.
|
| Yep, JavaScript is excellent as a multi-paradigm language. I
| pretty much use it as a functional one. I hear the JavaScript
| edition of SICP [1] works better than the Python one did.
|
| [1] https://mitpressbookstore.mit.edu/book/9780262543231
| ilaksh wrote:
| It's interesting to me how many people can look at the exact same
| thing and have completely different experiences and takeaways.
|
| I suggest that this is largely determined by things like the
| person's previous experiences, maybe even their group or personal
| identity, etc. and various factors that are not necessarily
| objective.
|
| For me, I strongly feel that using async/await consistently
| instead of a promise chain style can clean up a codebase quite a
| lot.
|
| I also feel that whitespace significant syntaxes are (to me)
| obviously cleaner. So for a year or two at least I used things
| like CoffeeScript or even ToffeeScript.
|
| But I got tired of feeling like I was swimming upstream.
|
| I also did a solo project with significant usage of LiveScript a
| few years ago. Which I think is a very nice language.
| spankalee wrote:
| Wait, what?
|
| The author sees this code and realizes it's unnecessarily serial:
| save('userData', userData) .then(() => save('session',
| sessionPrefences)) .then(() => ({ userData,
| sessionPrefences })
|
| But doesn't see that the async/await version is serial, even
| though it's arguably even _more_ obvious? await
| save('userData', userData); await save('session',
| sessionPrefences); return { userData, sessionPrefences }
|
| The transform to being parallel is pretty easy to read too:
| await Promise.all([ save('userData', userData),
| save('session', sessionPrefences) ]); return {
| userData, sessionPrefences }
|
| On top of that, the try/catch section is out of date. try/catch
| does not deoptimize the block in modern engines, and async/await
| absolutely integrates better with try/catch for much more
| bulletproof error handling.
___________________________________________________________________
(page generated 2024-08-13 23:01 UTC)