4 ms·
This is not just about performance. Unlike Go or Ruby, JavaScript is a single-threaded language. Synchronization constructs such as mutexes and semaphores are
by unscaled 2y ago
This is not just about performance.
Unlike Go or Ruby, JavaScript is a single-threaded language. Synchronization constructs such as mutexes and semaphores are uncommon and not part of any standard library. When you are calling a synchronous function, you can be completely assured that no race condition can develop while the function is running, but the same guarantee is not true for asynchronous functions.
That's why knowing which function is running asynchronously is even more useful for languages like JavaScript.
- regularfry 2y ago"Now you have two problems." Adding async/await was a hack to avoid having to tackle the hard problem of real concurrency, so now you have a) no real concurrency, and b) coloured functions. There is a parallel universe where JS added almost any other concurrency primitive and got a better trade-off than async/await.
- lolinder 2y agoHaving interacted with a bunch of other concurrency models in a bunch of other languages, I call. Concurrency is hard, every concurrency model has flaws, and which flaws you want to live with is largely a matter of personal preference and what type of problem you are working on. For myself, I happen to agree with OP that single threaded plus async/await has honestly been my favorite concurrency model I've ever worked with for the types of programs I tend to write (IO-bound web apps with lots of network requests and no hot loops). The property that they described of having no race conditions of any kind except where you opt into them by calling out to an explicitly tagged async function is an enormous unnecessary mental burden removed. In other contexts with higher compute demands, a full threading model or something like Go can make a ton of sense. But it's not worth the overhead for your average web app where what you really want is to just resume when that web request finishes. Use the right tool for the job, don't get dogmatic about programming language features in the abstract.
- unscaled 2y agoThere is a parallel universe in which Brendan Eich was given more than 10 days to hack on JavaScript, and in which he presented the language with full-blown stackful coroutines instead of hacky callbacks for concurrency. There might be even a parallel universe in which '[] + 0' produces an error rather than the string "0". Unfortunately, we do not live in that parallel universe, and JavaScript had to evolve the way it did. When callbacks have already been established as the standard concurrency mechanism, promises were a big improvement. When promises became standard, async/await was the natural evolution rather than breaking existing programs by introducing stackful coroutines. Other languages which chose async/await (Rust, C#, Kotlin) had their own reasons. In all of the cases above, the language designers wanted to give the user more control over the scheduler (Colorless Stackful coroutines require a global scheduler). In Rust's case, it's async/await state machines are just not wasting memory and unnecessarily copying it like stackful coroutines do (not to mention that Rust doesn't have a garbage collector and pointer rewrites, so it cannot just copy stacks around when they grow). In fact, Rust did have garbage collection and green threads, but they were both removed early on, as the language changed its focus to became a language where you can expect performance that are _at least_ on par with C or C++. In case of Kotlin, this all mostly had to do with finding an abstraction that can run on pre-Loom JVMs, JavaScript targets and native targets. Kotlin does not own the runtime. Saying everybody should follow Go here is silly. Not all languages share the same background and design constraints. Go-style Stackful coroutines require garbage collection, full control of the runtime and generally a language that is designed from scratch to have them - or a 6-year project to adapt your language and runtime to have them, like Project project Loom did. Async/await imposes some costs, but the benefits (compatibility, performance, flexibility) often outweigh these. After working with both async functions and stackful coroutines in multiple languages, I cannot say I ever felt concurrency in Go to be more ergonomic than Kotlin. If anything, it was the reverse for me in that particular case (owing to Go's lack of reification for goroutines and verbose syntax). Deciding in advance what color is my function (i.e. whether it needs to deal with I/O), is pretty simple and it rarely changes during the lifetime of a program (and even when it does, refactoring is not hard unless your design is bad). The only case where I did feel some pain using async/await, was Rust, and this has more to do with Pin, competing async runtimes (for a while), lack of support for async traits (for a while) and other areas where Async Rust still needs some work.