Lately during development at one of our clients, Ravello Systems, we decided we wanted better HTTP error handling.
Basically, our perfect solution would have generic handlers for errors, and most calls in the code will not have to do any special work for handling errors. This means that things like authentication problems, server unavailability issues, etc. will be handled in one place — like adding a generic “something went wrong” modal.
But, we also wanted to be able to easily override these default handlers so that specific places can do things like silently ignore errors of unimportant calls (e.g. background requests that aren’t user-facing), or display a special “try again later” context—aware message.
This is a case study of how we currently implemented this. I’m sharing this for others that might need it and in hopes to hear of alternative ways to accomplish it.
- The generic error handling should happen automatically, and can’t be something a developer can forget to enable. That means things like adding a ‘.catch(genericHandler)’ to every call is out of the question.
- When the less common case happens, meaning someone isn’t using the generic handler, it should be explicit.
- It should work seamlessly with angular’s $http service so that no matter if we aren’t the ones making the calls ourselves, everything should still be handled.
- It should allow us to easily handle grouped requests (i.e. requests that are handled by the caller in a single
How the end result looks
If you’re making a call that just wants to use the generic handling code, it looks like this:
1 2 3 4 5
Seems familiar? That’s right, in most cases you don’t need to change your code at all.
And in cases you do want to handle errors yourself:
1 2 3 4 5 6 7 8
As you can see, it’s pretty straightforward. And the use of a function (which we would essentially consider a block in languages like Ruby) allows us to group multiple requests in the same error handler.
Basically we tag every request that needs to be generically handled by adding a HTTP header to it. Then when requests fail due to errors an interceptor handles those that are tagged.
There are 3 parts:
A decorator for $http – This is the only way we came up with to tag requests inside our
specificallyHandled function. Interceptors are run asynchronously and so can’t tell whether the request was made inside our
specificallyHandled block or not. This decorator simply wraps all the
$http functions to add a specific header in cases they should be generically handled.
Response interceptor – This intercepts all the failed responses and handles them in a generic way – but only if they have the magical HTTP header we add in the
RequestsErrorHandler – A service that turns on or off the generic handling according to when it is called.
Also available here.