Try/except is not an antipattern, although it is probably an antipattern to turn an error condition into a boolean. But this happens in both examples, so that is a separate question.
Both status codes and exceptions are valid ways to handle HTTP error responses. The choice is a question of abstraction level.
If you are writing networking-level code (e.g. a proxy server), you probably want to treat all status codes on the same level of abstraction. A 4xx or 5xx status does not mean your program cannot fulfill its task, it is just a response which have to be handled appropriately.
If you are writing application level code, e.g some business logic which perform HTTP requests to fetch data, you want to separate network-level error conditions from the regular application logic, and exceptions are appropriate.
There are two separate questions: When is it appropriate to throw an exception, and when it is appropriate to catch an exception?
It is appropriate to throw an exception when a condition occurs which cannot be meaningfully handled at the level of abstraction of the current function. Of course this requires functions to be clear about abstraction levels, which might be the harder problem. Consider a function called sendHttpRequest(url, method, payload)
. A HTTP response code would be at the same level, so it would be reasonable to have as part of the return value.
But consider the function getCurrentExchangeRate(fromCurreny, toCurrency)
which internally calls some HTTP service. Clients calling this method would not be expected to know about HTTP response codes. It would therefore be appropriate for the function to throw an exception if it for whatever reason is not able to return the exchange rate.
Exceptions should only be caught when code is able to meaningfully handle the condition. If an exception cannot be handled at any level, then it should be allowed to rise to the top and terminate the application (fail fast principle).
Handling an exception means the code should be able to continue without fear of any state being corrupted by the operations which were not completed. (For example, returning 0 for the missing exchange rate would not be an appropriate way to handle the error, since this just leads to data corruption down the line. If in doubt, always fail fast.)
Catching an exception and returning false would almost never be a useful way to handle an exception or error condition, since it doesn't provide any information about what went wrong, and therefore no reasonable way for callers to handle the problem. A HTTP request could fail because of intermittent network errors, or it could fail because a hardcoded URL has a typo. These errors should be handled differently - network errors could be handled by re-trying the request, while the typo should fail fast, alerting developers to the problem.