Lightweight threads and channels are better than async/await syntax
# The pros of lightweight threads and channels
Lightweight threads don't have the cost of OS threads. You can launch a lot of them cheaply fast. Lightweight threads and OS threads can communicate with each other via channels. Lightweight threads can be freed from OS threads during IO/GPU-bound operations.
If you communicate between (lightweight) threads via channels, you get synchronous coding experience. There is no function color(async color + sync color).
Good examples of this synchronous concurrenct programming model are goroutine, clojure core.async, JVM virtual thread, janet fiber, and so on. Clojure core.async go block state machine doesn't require changes to runtime environment.
# The cons of lightweight threads and channels
Synchronous concurrency has a few trade-offs. I read that the changes required for built-in lightweight threads may make it difficult for other languages to interact with the affected language via FFI(foreign function interface). I suspect this can be overcome with future research?
Errors cannot be propagated outside threads, so you have to take care of exceptions in threads or pass error values to other threads. Go is famous for not having exceptions. Go only has panics like out-of-memory errors.
# The pros of async/await syntax
Unless you want to poll regularly as you would in C language for concurrent programming, async/await syntax doesn't require runtime environment changes that make it difficult for other languages to interact with the affected language via FFI.
However, core.async go blocks are macro-based state machines which don't require changes to runtime environment although macro-based state machines require a bit of care.
# The cons of async/await syntax
Async and await are contagious. Whatever touches async code becomes async.
# My comparison
Handling exceptions in isolated threads and passing error values to other threads is better than contagious async/await syntax. Error handling becomes a bit tedious with (lightweight) threads and channels, but handling exceptions as early as possible in each isolated thread reduces the possibility that unhandled exceptions crash the program.
If runtime environment changes required for built-in lightweight threads are not wanted, then we can research lightweight threads based on state machines, such as core.async go blocks.