codelord.net

Code, Angular, iOS and more by Aviv Ben-Yosef

Generic HTTP Error Handling in AngularJS

| Comments

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.

Our requirements

  • 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 $q.all() call).

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
$http.get('some/url').then(
    function(response) {
        // Stuff here
    }
);

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
RequestsErrorHandler.specificallyHandled(
    function() {
        $q.all({foo: FooService.fetch(), bar: BarService.fetch()}).then(
            function() { /* Handle success */ },
            function() { /* Handle specific errors */ }
        );
    }
);

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.

The implementation

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 $http decorator.

RequestsErrorHandler – A service that turns on or off the generic handling according to when it is called.

The Code

Also available here.

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

Comments