So why wouldn’t we simply throw and catch exceptions? The problem is one of context. At the point of an exception, you know the most intimate details about what went wrong. But you do not know the broader context. How does the calling subsystem or application want to deal with this particular kind of error? Since you don’t know the context, you throw the exception back out to someone who does.
At some higher level, you have enough context to know what to do with the error, but by the time you get there, you have lost the context to continue. The stack has unwound, partial work has been lost, and you are left to pick up the pieces. Or, more likely, to give up on the application-level task that you started.