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.
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.
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.
With the benefit of hindsight, explicit handling and unwinding has proven to be safer and more reliable.