Hacker News new | ask | show | jobs
by arp242 1104 days ago
I think async-by-default was an interesting idea and well worth trying, but I don't really think it was a good idea. Turns out that most things you want to do are synchronous, and "sync-by-default unless mentioned otherwise" makes a lot more sense. This includes I/O by the way, because most of the time you want to wait until an operation has finished.

Or to put it in another way: JavaScript makes the uncommon case easy and the common case hard(er).

2 comments

I disagree. I'd take JS-style promises over trying to manage Futures in ForkJoinPools or thread pools any day. Being able to write async expressions in parallel by default means even junior devs take advantage of parallelism. I've seen plenty of code written in Java and Ruby where multiple network and DB requests are made in serial despite having no dependency on each other. The usual reason is that there's just a lot more friction to have it be parallel there.
I have also found Promises very hard to reason about at times: "okay, so I have some code here, and when exactly is this run?" can be a difficult question to answer with Promises. Part of that is inherent in async code, but part of that is also because IMHO Promises make it harder than it needs to be.

I never really used Java, but I have used Ruby and Python (IIRC Python's APIs were modelled on the Java ones) and I agree it can be painful. The thing is, even with an awkward async implementation it's something you have to deal with relatively infrequently when synchronous is the default (as it is in most languages). When you do it can be a pain, but I'd rather have this "occasional pain" vs. "pain every time I want to do any I/O operation".

Personally I like how Go does things.

My code is running <- no await

My code is accessing another resource (storage, network, etc.) <- await

It's really not that complicated. If you're surprised by the presence of absence of a Promise, you might want to take a moment to understand what is being processed. There's a good chance there's a gap there that extends beyond a simple keyword in JS.

> I have some code here, and when exactly is this run?

If there are `await` keywords previously in the function, then the line you're looking at will run after these async calls are done. Otherwise it'll run ASAP. Is there something else to it?

People often get confused because they expect `await` to sequence promise resolution too. For example

    const example = async () => {
        const ifError = Promise.reject("something went wrong")
        const value = await someOtherPromise()
        await (valueIsOk(value) ? runNextStep(value) : ifError)
    }
will always throw.
I don't think I follow. Your example left out all the definitions of these functions, so you can't really deterministically say what will happen. If `someOtherPromise()` fulfills, `valueIsOk(value)` evaluates to `true` or truthy, and `runNextStep(value)` fulfills, `example` will fulfill and not reject. If any of those conditions don't hold, `example` settles as rejected.
The issue is that `ifError` throws whether `example` fulfills or not. Promised values are sequenced by `async`, but promise side-effects are sequenced like side effects of any other javascript statement.
Async doesn't give you parallelism by default though, you just get concurrency. You don't get parallelism without using Workers.
Well, I wouldn't put it like that. Async can trivially cash out into parallel work like if you're just sending queries to a database or ffmpeg or ImageMagick or, frankly, most common use cases which are going to do work in parallel out of process.

All of your I/O work could even be happening in parallel in a run of the mill JS app. Workers just give you parallelism with your sync JS code which is a more narrow claim.

Fair, I should've been more careful with the way I worded that. The common example I was highlighting was concurrent network IO requests that effectively resolve to parallel work that runs on different nodes. With a service oriented architecture, this can be the norm rather than the exception.
And workers get you isolation, no shared memory. You must explicitly pass data ownership from one thread to the next. (And I consider all of that a good thing.)
async-await is not the best solution for most things, but it is usually the best solution for UI and I/O (database interactions, data-fetching, file-system, etc)

Which makes it default-best for (and I speculate here) 90% of the code that is written because it covers client-side and server-side API layers. Ie business logic. Like you mention this kind of code is very rarely synchronous (in the sense that you want to block the execution thread until you get the result). An UI needs to keep answering to users inputs and an API server needs to keep answering requests while waiting on I/O

The places where it isn't good are usually the kind of software that can be packaged and reused (libraries, ie the heavy lifting software). Things like databases, image processing, neural networks, etc

Talking about JS specifically there are a few more use-cases where it isn't good even though an async-await system makes sense, like embedded low-memory environments

IMO the main problem with JS is not JS, it is the browsers DOM. It is about time we get a replacement.