Hacker News new | ask | show | jobs
by lofenfew 672 days ago
>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.

2 comments

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.

That's basically how goroutines work in Go. You opt into concurrency with the `go` keyword, while it's blocking by default. While in JS it's concurrent by default and you opt-in to blocking with the `await` keyword. (Except in Go you have true parallelism for CPU-bound tasks too, while in JS it's only for I/O)

Both have their pros and cons. I've seen problems in Go codebases where some I/O operation blocks the main thread because it's not obvious through the stack that something _should_ best be run concurrently and it's easy to ignore until it gets worse (at which point it's annoying to debug).

This is I believe how ziglang works on its asynchronous story
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.
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()
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".
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.
If this "obscures the call order" then isn't that a problem with the programming language in general? If these functions weren't async, would you also not be willing to use g(f()), as that "obscures the call order"? (I certainly have met people who do.) If we aren't going to use function calls, and we also do not want to put these functions on separate lines as that is "bulky", then are we going to argue that we should have something similar to .then() for synchronous code?...

My argument here is that JavaScript is -- as well as most languages we use these days are -- fundamentally designed around the syntax of a function call, and so if you program in such for very long at all you get used to reading "inside out, right-to-left", as that's how function calls work. If we do not like inside out execution order, then we should fix the entire language (by using suffix notation... fwiw, I'd be totally on board ;P), rather than narrowly complain about it only in code which uses async/await and fixing it by abusing the OOP this argument exception to the ordering rule.

(I would also point out that the special syntax order exception you are relying on only works for a single argument, and so if you have to pass task2 both the result of task1 and a second argument to configure it, the usage of .then() breaks down unless we have a way to curry arguments, which we can always simulate using JavaScript's bind(), but at some point we are really going far out of our way to avoid coding using function calls, despite being in a language which relies on them everywhere else.)

At the end of the day, I am just looking for some consistency, so I can quickly and easily interpret what code does no matter whether it is synchronous or asynchronous and whether it takes zero, one, or more inputs.

    const value1 = await task1(init);
    const value2 = await task2(value1);