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