It's not that they can't be used productively. It's that they probably do more harm than good on balance. And I think async mania is getting there. It was a revelation when node showed it to us 15 years ago. But it's evolved in bad directions, IMHO.
But "invented" and "revealed" are different verbs for a reason. The release of node.js and it's pervasively async architecture changed the way a lot of people thought about how to write code. For the better in a few ways. But the resulting attempt to shoe-horn the paradigm into legacy and emerging environments that demanded it live in a shared ecosystem with traditional "blocking" primitives and imperative paradigms has been a mess.
I suspect a modern CPU has a branch instruction saying "This branch will never be taken except in exceptions, so assume this branch is not taken". But I must admit I haven't seriously looked at assembly language for some time.
(EDIT: yes, modern CPUs including x86 and ARM allow the programmer/compiler to hint if a branch is expected to be taken).
> Also, many branches will increase code size.
I'd like to see some data on that. Of course branches take code size, but how much is that percentage-wise? I suspect not much.
Basically, the overhead of exceptions is probably less than handling the same errors manually in any non-trivial program.
Also, it's not like these table doesn't exist in other languages. Both Rust and Go have to unwind.
With the benefit of hindsight, explicit handling and unwinding has proven to be safer and more reliable.