In my opinion, exceptions are an essential tool for detecting code errors at run time. Both in tests and in production. Make their messages verbose enough so in combination with a stack trace you can figure out what happened from a log.
Exceptions are mostly a development tool and a way to get reasonable error reports from production in unexpected cases.
Apart from separation of concerns (happy path with only expected errors vs. falling through until reaching some generic handler for unexpected errors) being a good thing, making your code more readable and maintainable, it is in fact impossible to prepare your code for all possible unexpected cases, even by bloating it with error handling code to complete unreadability.
That's actually the meaning of "unexpected".
Btw., what is expected and what not is a decision that can only be made at the call site. That's why the checked exceptions in Java didn't work out - the decision is made at the time of developing an API, when it is not at all clear what is expected or unexpected.
Simple example: a hash map's API can have two methods:
the first throwing an exception if not found, the latter giving you an optional value. In some cases, the latter makes more sense, but in others, your code sinmply must expect there to be a value for a given key, so if there isn't one, that's an error that this code can't fix because a basic assumption has failed. In this case it's actually the desired behavior to fall out of the code path and down to some generic handler in case the call fails.
Code should never try to deal with failed basic assumptions.
Except by checking them and throwing well readable exceptions, of course.
Throwing exceptions is not evil but catching them may be. Don't try to fix unexpected errors. Catch exceptions in a few places where you wish to continue some loop or operation, log them, maybe report an unknown error, and that's it.
Catch blocks all over the place are a very bad idea.
Design your APIs in a way that makes it easy to express your intention, i.e. declaring whether you expect a certain case, like key not found, or not. Users of your API can then choose the throwing call for really unexpected cases only.
I guess that the reason that people resent exceptions and go too far by omitting this crucial tool for automation of error handling and better separation of concerns from new languages are bad experiences.
That, and some misunderstanding about what they are actually good for.
Simulating them by doing EVERYTHING through monadic binding makes your code less readable and maintaintable, and you end up without a stack trace, which makes this approach WAY worse.
Functional style error handling is great for expected error cases.
Let exception handling automatically take care of all the rest, that's what it's for :)