Ideas on Improving Error Handling in Javascript

I like Javascript. I like it a lot. But of late, I’m really annoyed by the state of Error Handling in the language. There are some tools that improve the situation some of the times. Promises and Reactive Extensions both give you a simple way to handle all your errors in one place. This is pretty good of course, no one wants to keep checking for errors everywhere, but there are deeper problems. What is an error after all?

Usually when we speak of Errors we are talking about exceptions. Javascript is pretty good at handling exceptions. You can throw an exception from anywhere. You can use try-catch to catch exceptions from anywhere as well.

So what’s the problem? The problem is that you that while you can throw values, you don’t really want to. While you can use try-catch to catch non-Error values and use it as a way to do control flow, it’s really not the best way to do thing. And it can be really really verbose and full of ceremony to handle errors in a sensible way.

Let me give you a practical example that I ran into while working on Upclose.me. Upclose is a React + Flux app. The Flux parts are relatively new and they really cleaned up the data fetching and storing bits of the apps. However, there is now a new problem. When I try to fetch a particular resource, say a user profile, there can be a few possible error states — the network call failed; I got a 404 error, it’s a private profile and I got a 403 error, the API server had an internal 500 error, etc. For my AJAX library, it’s just an error. It just throws it.

Now I have to manually check for each case and handle them independently. Further, each case may require a different action. I’ve tried but abstracting away the entire error handling code into a single place, just doesn’t work. There is way too much custom handling depending on the resource that needs to be done.

What we need is smaller abstractions.

Luckily the tools we need, already exist in Javascript. It just needs some thought and discipline: In particular the tools that we need are: Custom Types and higher-order-functions

You may be confused with one or both of them. Let me unwrap that for you:

Javascript is a dynamically typed language, but it is typed. And though we can’t define custom types, we can easily define Classes.

Higher-order-functions are something that are so common in Javascript that if you don’t know the term, it’s only because you’ve used them so often you never even needed to learn about them.

Higher-order functions are really just functions that accept functions as arguments or return them. In this case, I’m talking in particular about functions that take a function, transform it and return the new function. Let’s deal with a simple example.

Consider this pseudo Node.js code, stuck in a universe without Promises, Rx, Async Generators etc. How can we do error handling in Node with Callbacks? So far we would write something like this:

function doSomethingAsync(args, callback){

  asyncOp1(args, function(err, value){
    if(err){
      callback(err)
    } else {
      asyncOp2(args2, function(err, value2){
        if(err){
          callback(err)
        } else {
          callback(value2)
        }
      })
    }
  })

}

I didn’t even need to go past the second nested async operation for me to make the point that we are repeating a LOT of code:

if(err){callback(err)}

Those of us who use callbacks in our code, don’t even thing about it. The only time we stop writing code like this is when we finally understand Promises, and leave callbacks behind altogether.

But callbacks, though still bound to lead you into the pyramid of doom, are better than this.

The solution is actually very simple. It’s just a Higher-Order-Function. This is how you can abstract away the error handling code:

function doSomethingAsync(args, callback){

  function handleErrors(fn){
    return function(err, value){
      if(err) {
        callback(err)
      } else {
        fn(value)
      }
    }
  }

  asyncOp1(args, handleErrors(function(value){
    asyncOp2(args, handleErrors(function(value2){
      callback(value2)
    }))
  }))

}

Isn’t that a really simple way to take away all your error handling code and put it in one place? This would save you from typing the if-else code EVERY SINGLE TIME.

But for now, we’ve made something that can handle the errors in one function. What if we could make this utility work everywhere in our node code? This is a common enough pattern. We have to do some async operation and call callback when we are done. The callback takes a possible error as it’s first argument. Conventions are great!

The only thing we need to do is make a function that can take a dynamic callback method to call when there is an error. Easy:

function handleErrorsBoundTo(callback){
  function (fn){
    return function(err, value){
      if(err) {
        callback(err)
      } else {
        fn(value)
      }
    }
  }
}

function doSomethingAsync(args, callback){

  var handleErrors = handleErrorsBoundTo(callback)

  asyncOp1(args, handleErrors(function(value){
    asyncOp2(args, handleErrors(function(value2){
      callback(value2)
    }))
  }))

}

Now, you can just create a custom handle errors function just by passing the final callback to the handleErrorsBoundTo function. The good thing is that you can still do custom error handling method. handleErrorsBoundTo just takes a function that actually handles your errors.

Another way to think about it is that you already have the error handling benefits of Promises in you callback code.


Now let’s talk about Custom Types for errors. Node has a convention of putting errors first. I want to propose a more flexible convention. One that can work for synchronous functions as well, and can work with just a single argument and return value as well.

Why not just check if the value being passed in is an error or not? The way to do that is actually very simple:

err instanceof Error

That’s it. We can make a new type of handleErrors function that can check if the value being passed in is an Error or not. If it’s an error, we just return the Error. If it’s not an error, our intended function can run.

function handleErrors(fn){
  return function(arg){
    if(arg instanceof Error){
      return arg
    } else {
      return fn(arg)
    }
  }
}

This function can be very powerful for Error handling in Javascript code. This combined with async version of our error handling code can help us use Errors as useful control flow tools.

Very often in our code, we reach a place where, if something happens, we need to jump a whole bunch of code and jump straight to the end of the code. So far, we’ve had to to this with messy if-else. We’ve had to purposely throw errors in our Promise chain just to skip a few steps.
This code can take all of that away. We can pass value from function to function, and the errors will just pass through all of them untouched. We just transform all the functions with our handleErrors function.

Here’s the problem though. The Error class is very limited. It comes with some good properties like stack traces, but that is of little use to us when we want to use Errors as values and as a tool for control flow. The native Error type in Javascript can only store strings. And those too, are pretty much destroyed in the process.

You can store real data in you Error objects and then get those values out on the other end.

The solution — our very own Error types:

function ErrorContainer(value){
   this.value = value
}

And that’s it! We can now update our handleErrors function to check for this type of “Errors”:

function handleErrors(fn){
  return function(arg){
    if(arg instanceof Error || arg instanceof ErrorContainer){
      return arg
    } else {
      return fn(arg)
    }
  }
}

Now, just like that, whenever we do handle our errors, we can actually get original value out of our errors.

Further, we can make subclasses of HandleErrors and then create custom errors depending on our business logic. Going back to the example in Upclose, I can make different Error types for the various types of Errors that can happen when trying to make an AJAX request. And I can handle them in one place, just the way I need to.

Important Note #

The ideas in this blog post aren’t new in anyway. This has been the way that errors are handles in many functional languages like F#. The ideas for this post were stolen shamelessly from Railway-Oriented Programming with F#.

Further, I haven’t been able to make the value of this idea quite clear, as that would probably make this blog post a whole lot longer, but try it, I’m sure you’ll see the value.

 
57
Kudos
 
57
Kudos

Now read this

Browserify VS Webpack - JS Drama

A simple Gist by Substack, the author of Browserify has caused much drama in the javascript community. A lot has been said about modularity vs the kitchen-sink approach. While on the one hand, Substack talks about how bundling features... Continue →