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.
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
fun background(): Channel<String> {
val ch = Channel<String>()
GlobalScope.launch {
delay(5000)
ch.send("OK")
}
return ch
}
fun getWebsiteData(url: String): Channel<String> {
val ch = Channel<String>()
GlobalScope.launch {
delay(4000)
ch.send("website data")
}
return ch
}
fun work(): Channel<String> {
val ch = Channel<String>()
GlobalScope.launch {
delay(1000)
ch.send("work result")
}
return ch
}
fun main() = runBlocking {
println("START")
val bck = background()
val urls = listOf("www.web1.com", "www.web2.com", "www.web3.com", "www.web4.com")
val chs = urls.map { getWebsiteData(it) }
generateSequence { work() }.take(4).forEach {
println("sync job done: " + it.receive())
}
chs.forEach {
println("data fetched done: " + it.receive())
}
println("background done:" + bck.receive())
println("END")
}Regarding "C# approach where async behavior looks sync", I don't follow you here. C# is very explict with async's returning Task<> where with Go's lack of "colored" fns, for example, you don't really know when you code is async or not.
Yes - but there isn't much choice in the mainstream. Sure, I'd rather use .net core/Kestral, but if you want back-end JS/TS then Node is it, unless your org let's you experiment w/Deno or other.
More to the point I think, is that Node (and Deno, FWICT) lack general native or green thread support for true multi-processing without serialization to separate clusters, so you are forced to use async and timers for long-running or parallel work.