I agree async is a mess, but for web backends what is wrong with multi-process?
I do not think JS got it right. Node did, by doing async, but the reason for that was that JS did not do threads! It was making a virtue of a deficiency.
JS didn't do threads for a reason, though. It's not that the people working on JS had never heard of threads. Java, which JS was named after, was pervasively multithreaded from the beginning. The Microsoft IE folks lived and breathed threads. Opera even had multithreaded JS in 02000 before they took it out.
JS didn't do threads because threads are an error-prone way to write concurrent software. Crockford was a huge influence on its development in the early 02000s, and he had been at Electric Communities; he was part of the capabilities cabal, centered around Mark S. Miller, who literally wrote his dissertation on why you shouldn't use threads and how to structure async code comprehensibly. Promises came from his work, by way of Twisted. Unfortunately, a lot of that work didn't get into JS until well after Node had defined a lot of its APIs.
But this wasn't "making a virtue of a deficiency". JS was intentionally exploring a different approach to structuring concurrent software. You might argue that that approach didn't pay off, but it wasn't some kind of an accident.
I would say that it would be better to offer both options, depending on what you are doing. Not so much for JS's original role in the browser, but threads can be the right approach for a lot of backend tasks.
TCL, which promoted the same approach for the same reasons long before Node, eventually added threading.
That's an excellent point about Tcl; I'm sure it was a strong influence on Brendan's design for JS, though maybe not as strong as Perl, Java, and Scheme.
We clearly need some way to take advantage of manycore, but
I'm still not convinced that threading with implicitly shared mutable state is the right default. It isn't even what the hardware implements! Every core has its own cache! It's a better fit to the hardware than a single giant single-threaded event loop is, and I think that accounts for its curent dominance, but there are a lot of other possibilities out there, like transactional memory, explicit asynchronous message-passing interfaces, or (similarly) lots of tiny single-threaded event loops like Erlang (or, maybe, like Web Workers).
Web backends tend to have lots of concurrent connections doing IO-bound things. Processes or OS threads have too much overhead for this; they're more for when you want CPU parallelism with some communication between threads. Thread pools are still only a compromise. So JS with event loops made a lot of sense.
Greenthreading like in Golang is even better cause you get the advantages of OS threads, but that requires a runtime, which is why Rust didn't do that. And I guess it's hard to implement, cause Java didn't have it until very recently.
An early Java version had greenthreads, but they were soon removed in like the year 2000. That's pretty much the reason all our Java code at work uses some kind of cooperative multitasking with that painful promises syntax (foo().then(...))
I do not think JS got it right. Node did, by doing async, but the reason for that was that JS did not do threads! It was making a virtue of a deficiency.
I love whitespace for scope.