Legacy error handling tends to follow the method that all functions return a code depending on success/failure. You would check this code and handle (if an error) appropriately
Ignoring an exception requires action by the developer while ignoring a bad returning value requires exactly 0 action. This, in theory, makes it more likely a developer will handle an error vs. ignoring it or not even realizing it was happening.
Provides a cleaner separation between the point of an error and the handling. It doesn't force manual propagation of the error at every point in between.
Exceptions can a larger and richer information payload than a simple error code. There are ways to do this with error codes but it's more of an afterthought and is a bit cumbersome.