The alternative is to write SansI/O code (
https://sans-io.readthedocs.io/), so that your program don't have to think about that.
Besides, you don't have to put async/await everywhere: if your code is not performing IO, it completely ignore this concern.
The problem is that most of your code is mixing I/O and non I/O code, and people just don't think about it. E.G: a django website is not just a web server, but has also plenty of calls to the session store, the cache backend, the ORM, etc.
Now you could argue that the compiler/interpreter is supposed to hide the sync/async choice to the code user. Unfortunately, this hides where the concurrency happens, and things have dependencies on each others. Some are exclusive, some must follow each others, some can be parallel but must all finish together at some points, some access concurrent resources...
You must have control over all that, and for that to happen, you can either:
- have guard code around each place you expect concurrency. This is what we do with threads, and it sucks. Locking is hard, and you always miss some race condition because it can switch anywhere.
- have implicit but official switch points and silos you must know by heart. This is what gevent does. It's great for small systems, not so much at scale.
- have explicit switch points and silos: async/await, promises, go-routine. This is tedious to write, but the dangerous spots are very clear and it forces you to think about concurrency upfront.
The last one is the least worse system we managed to write.