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.
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?
const example = async () => {
const ifError = Promise.reject("something went wrong")
const value = await someOtherPromise()
await (valueIsOk(value) ? runNextStep(value) : ifError)
}
will always throw.Is the concern you're raising that people may accidentally orphan floating promises? That can be addressed with linter rules. [1][2]
[1]: https://github.com/typescript-eslint/typescript-eslint/blob/...
[2]: https://github.com/typescript-eslint/typescript-eslint/blob/...
Tell that to my junior coworkers. It's probably the single most common cause of async bugs in our codebase.
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.
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.