I'm not being unfair when I enumerate the most popular languages for comparison. When people crap on Javascript, they presumably prefer another language. And C#/Kotlin aren't exactly the top picks.
Kotlin has BYOB coroutines which are hard to work with. People don't use them. Going with the C# approach where async behavior looks sync was a bad move. I predict Kotlin's coroutines will never be a centerpiece abstraction just like how people don't really use Go's channels (people in practice just go back to Mutexes).
I mean, try it. Write the equivalent to this in Kotlin:
// get background work started now
const background = promise()
// crawl some urls concurrently as well, just 4 at a time
const crawl = Promise.map(urls, crawl, { concurrency: 4 })
// while that's going on, we have some work that
// we must get done.
for (const task of tasks) {
await worker(task)
}
// worker's done, now we can wait on
// the crawler and background work.
const [a, b] = await Promise.all([
crawl.then(processResults),
background
])
CSP never caught on because after you have more than one channel as a central bus (the toy architecture), you immediately descend into channel hell. In-channels, out-channels, channels over channels. Back to using pencil and paper and scouring your code to decode the classic buffer bloat problem.
A single-threaded event loop with a central promise abstraction is a great way to write networked code.
Btw, I use Kotlin in a large JVM project and I'm stuck with the horror of https://docs.oracle.com/javase/8/docs/api/java/util/concurre.... That's more likely what you'll be doing day to day with Kotlin, not playing with its toy coroutines.