DEV Community

Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

Elegant error handling with the JavaScript Either Monad

Let’s talk about how we handle errors. JavaScript provides us with a built-in language feature for handling exceptions. We wrap problematic code in try...catch statements. This lets us write the ‘happy path’ in the try section, and then deal with any exceptions in the catch section. This is not a bad thing. It allows us to focus on the task at hand, without having to think about every possible error that might occur. It’s definitely better than littering our code with endless if-statements.

Without try...catch, it gets tedious checking the result of every function call for unexpected values. Exceptions and try...catch blocks serve a purpose but they have some issues. And they are not the only way to handle errors. In this article, we’ll take a look at using the ‘Either monad’ as an alternative to try...catch.

A few things before we continue. In this article, we’ll assume you already know about function composition and currying. If you need a minute to brush up on those, that’s totally OK. And a word of warning, if you haven’t come across things like monads before, they might seem really… different. Working with tools like these takes a mind shift.

Don’t worry if you get confused at first. Everyone does. I’ve listed some other references at the end that may help. But don’t give up. This stuff is intoxicating once you get into it.

A sample problem

Before we go into what’s wrong with exceptions, let’s talk about why they exist. There’s a reason we have things like exceptions and try…catch blocks. They’re not all bad all of the time.

To explore the topic, we’ll attempt to solve an example problem. I’ve tried to make it at least semi-realistic. Imagine we’re writing a function to display a list of notifications. We’ve already managed (somehow) to get the data back from the server. But, for whatever reason, the back-end engineers decided to send it in CSV format rather than JSON. The raw data might look something like this:

https://medium.com/media/340b19bf941a0c198bd6abda56fd78a2/href

Now, eventually, we want to render this code as HTML. It might look something like this:

https://medium.com/media/b95503fbfdaeb6b5e261ac54df36b165/href

To keep the problem simple, for now, we’ll just focus on processing each line of the CSV data. We start with a few simple functions to process the row. The first one we’ll use to split the fields:

https://medium.com/media/9041c20bb82f981bf081ff83b07bdeb4/href

Now, this function is oversimplified because this is a tutorial about error handling, not CSV parsing. If there’s ever a comma in one of the messages, this will go horribly wrong. Please do not ever use code like this to parse real CSV data. If you ever do need to parse CSV data, please use a well-tested CSV parsing library.

Once we’ve split the data we want to create an object, where the field names match the CSV headers. We’ll assume we’ve already parsed the header row somehow. Note that we throw an error if the length of the row doesn’t match the header row (and _.zipObject is a lodash function):

https://medium.com/media/4447001360fcc70927994771b1836155/href

After that, we’ll add a human-readable date to the object, so that we can print it out in our template. It’s a little verbose, as JavaScript doesn’t have awesome built-in date formatting support. Note that it throws an error for an invalid date:

https://medium.com/media/c8e0a8c09127c69b12ff7b405b7a8db6/href

And finally, we take our object and pass it through a template function to get an HTML string:

https://medium.com/media/6269155e3c2ac365e3b314303923760b/href

If we end up with an error, it would also be nice to have a way to print that too:

https://medium.com/media/35c26805f7f48939d5aef59020a5299a/href

And once we have all of those in place, we can put them together to create our function that will process each row:

https://medium.com/media/6d05637f3d6423baf3e69e82b49a7745/href

So, we have our example function. And it’s not too bad, as far as JavaScript code goes. But let’s take a closer look at how we’re managing exceptions here.

Exceptions: The good parts

So, what’s good about try...catch? The thing to note is, in the above example, any of the steps in the try block might throw an error. In zipRow() and addDateStr() we intentionally throw errors. And if a problem happens, then we simply catch the error and show whatever message the error happens to have on the page. Without this mechanism, the code gets really ugly. Here’s what it might look like without exceptions. Instead of throwing exceptions, we’ll assume that our functions will return null:

https://medium.com/media/bdf53951b44c25097664e1911a251fb7/href

As you can see, we end up with a lot of boilerplate if-statements. The code is more verbose. And it’s difficult to follow the main logic. Also, we don’t have a way for each step to tell us what the error message should be, or why they failed. (Unless, that is, we do some trickery with global variables.) So, we have to guess, and explicitly call showError() if the function returns null. Without exceptions, the code is messier and harder to follow.

But look again at the version with exception handling. It gives us a nice clear separation of the ‘happy path’ and the exception handling code. The try part is the happy path, and the catch part is the sad path (so to speak). All of the exception handling happens in one spot. And we can let the individual functions tell us why they failed. All in all, it seems pretty nice. In fact, I think most of us would consider the first example a neat piece of code. Why would we need another approach?

Problems with try…catch exception handling

The good thing about exceptions is they let you ignore those pesky error conditions. But unfortunately, they do that job a little too well. You just throw an exception and move on. We can work out where to catch it later. And we all intend to put that try…catch block in place. Really, we do. But it’s not always obvious where it should go. And it’s all too easy to forget one. And before you know it, your application crashes.

Another thing to think about is that exceptions make our code impure. Why functional purity is a good thing is a whole other discussion. But let’s consider one small aspect of functional purity: referential transparency. A referentially-transparent function will always give the same result for a given input. But we can’t say this about functions that throw exceptions. At any moment, they might throw an exception instead of returning a value. This makes it more complicated to think about what a piece of code is actually doing. But what if we could have it both ways? What if we could come up with a pure way to handle errors?

Coming up with an alternative

If we are going to write our own pure error handling code, then we need to always return a value. So, as a first attempt, what if we returned an Error object on failure? That is, wherever we were throwing an error, we just return it instead. That might look something like this:

https://medium.com/media/38a818917a3be3473f045a6ee38fb798/href

This is only a very slight improvement on the version without exceptions. But it is better. We’ve moved responsibility for the error messages back into the individual functions. But that’s about it. We’ve still got all of those if-statements. It would be really nice if there was some way we could encapsulate the pattern. In other words, if we know we’ve got an error, don’t bother running the rest of the code.

Polymorphism

So, how do we do that? It’s a tricky problem. But it’s achievable with the magic of polymorphism. If you haven’t come across polymorphism before, don’t worry. All it means is ‘providing a single interface to entities of different types.’¹ In JavaScript, we do this by creating objects that have methods with the same name and signature. But we give them different behaviors. A classic example of this is application logging. We might want to send our logs to different places depending on what environment we’re in. So, we define two logger objects:

https://medium.com/media/37efffc0d3492fa4b6490a8309edfaa6/href

Both objects define a log function that expects a single string parameter. But they behave differently. The beauty of this is that we can write code that calls .log(), but doesn’t care which object it’s using. It might be a consoleLogger or an ajaxLogger. It works either way. For example, the code below would work equally well with either object:

https://medium.com/media/df6ac0da3521f57cebe2fe60b8d31b8b/href

Another example is the .toString() method on all JS objects. We can write a .toString() method on any class that we make. So, perhaps we could create two classes that implement .toString() differently. We’ll call them Left and Right (I’ll explain why in a moment):

https://medium.com/media/d35a622b9e35d9f458d6b521f503a171/href

Now, let’s create a function that will call .toString() on those two objects:

https://medium.com/media/b48360f1a44c8faecc03ff558a2fb1b2/href

Not exactly mind-blowing, I know. But the point is that we have two different kinds of behavior using the same interface — that’s polymorphism. But notice something interesting. How many if-statements have we used? Zero. None. We’ve created two different kinds of behavior without a single if-statement in sight. Perhaps we could use something like this to handle our errors…

Left and right

Getting back to our problem, we want to define a happy path and a sad path for our code. On the happy path, we just keep happily running our code until an error happens or we finish. If we end up on the sad path though, we don’t bother with trying to run the code anymore. Now, we could call our two classes ‘Happy’ and ‘Sad’ to represent two paths. But we’re going to follow the naming conventions that other programming languages and libraries use. That way, if you do any further reading it will be less confusing. So, we’ll call our sad path ‘Left’ and our happy path ‘Right’ just to stick with convention.

Let’s create a method that will take a function and run it if we’re on the happy path, but ignore it if we’re on the sad path:

https://medium.com/media/18f667743a59920f2a78da1165c5885e/href

Then we could do something like this:

https://medium.com/media/ca19fa021f2b0983e4ef4821aac883d3/href

Map

We’re getting closer to something useful, but we’re not quite there yet. Our .runFunctionOnlyOnHappyPath() method returns the _value property. That’s fine, but it makes things inconvenient if we want to run more than one function. Why? Because we no longer know if we’re on the happy path or the sad path. That information is gone as soon as we take the value outside of Left or Right. So, what we can do instead is return a Left or Right with a new _value inside. And we’ll shorten the name while we’re at it. What we’re doing is mapping a function from the world of plain values to the world of Left and Right. So we call the method .map():

https://medium.com/media/6be531475ef2871a287f0fed15d3c8af/href

With that in place, we can use Left or Right with a fluent-style syntax:

https://medium.com/media/d3100ae4de5b9a9b204a0fe9202226f8/href

We’ve effectively created two tracks. We can put a piece of data on the right track by calling new Right() and put a piece of data on the left track by calling new new Left().

If we map along the right track, we follow the happy path and process the data. If we end up on the left path though, nothing happens. We just keep passing the value down the line. If we were to say, put an Error in that left track, then we have something very similar to try…catch.

As we go on, it gets to be a bit of a pain writing ‘a Left or a Right’ all the time. So we’ll refer to the Left and Right combo together as ‘Either’. It’s either a Left or a Right.

Shortcuts for making Either objects

So, the next step would be to rewrite our example functions so that they return an Either. A Left for an Error, or a Right for a value. But, before we do that, let’s take some of the tedium out of it. We’ll write a couple of little shortcuts. The first is a static method called .of(). All it does is return a new Left or Right. The code might look like this:

https://medium.com/media/e9585cc2a9be5c136553f2b9e4593ad2/href

To be honest, I find even Left.of() and Right.of() tedious to write. So I tend to create even shorter shortcuts called left() and right():

https://medium.com/media/dd69baec53300493f1b8870a0e5a2d67/href

With those in place, we can start rewriting our application functions:

https://medium.com/media/ad5ef59873a69abe89b648701ca177be/href

The modified functions aren’t so different from the old ones. We just wrap the return value in either Left or Right, depending on whether we found an error.

With that done, we can start re-working our main function that processes a single row. We’ll start by putting the row string into an Either with right(), and then map splitFields() to split it:

https://medium.com/media/9a2dd858d5cabc13b4cf6b3a93d7871a/href

This works just fine, but we get into trouble when we try the same thing with zipRow():

https://medium.com/media/b6076b2d08e1ccf20eed0bfd6deb15dc/href

This is because zipRow() expects two parameters. But functions we pass into .map() only get a single value from the ._value property. One way to fix this is to create a curried version of zipRow(). It might look something like this:

https://medium.com/media/30e571a8dae1508d724b6051a3cc14bf/href

This slight change makes it easier to transform zipRow() so it will work nicely with .map():

https://medium.com/media/6034ed8b7b4930d8e86099f068b7fec9/href

Join

Using .map() to run splitFields() is fine, as splitFields() doesn’t return an Either. But when we get to running zipRow() we have a problem. Calling zipRow() returns an Either. So, if we use .map() we end up sticking an Either inside an Either. If we go any further we’ll be stuck unless we run .map() inside .map(). This isn’t going to work so well. We need some way to join those nested Eithers together into one. So, we’ll write a new method, called .join():

https://medium.com/media/e0887a1b66254ef18d9cd43ce749e564/href

Now we’re free to un-nest our values:

https://medium.com/media/7f49b6b32e6efcc4ab0585a27a9b15c6/href

Chain

We’ve made it a lot further. But remembering to call .join() every time is annoying. This pattern of calling .map() and .join() together is so common that we’ll create a shortcut method for it. We’ll call it .chain() because it allows us to chain together functions that return Left or Right:

https://medium.com/media/45be202fc4da5001d6b3832528407a9b/href

Going back to our railway track analogy, .chain() allows us to switch rails if we come across an error. It’s easier to show with a diagram though.

With that in place, our code is a little clearer:

https://medium.com/media/62496340fceceaf82f1908ed4b602b64/href

Doing something with the values

We’re nearly done reworking our processRow() function. But what happens when we return the value? Eventually, we want to take different action depending on whether we have a Left or Right. So we’ll write a function that will take different action accordingly:

https://medium.com/media/a8e48b5c8d573c3d48abfbf40a6d4697/href

We’ve cheated and used the inner values of the Left or Right objects. But we’ll pretend you didn’t see that. We’re now able to finish our function:

https://medium.com/media/08800e7cf451e7e40aceac729a45e8e9/href

And if we’re feeling particularly clever, we could write it using a fluent syntax:

https://medium.com/media/46cbf2a13813e27a2f59136d9fa84c28/href

Both versions are pretty neat. Not a try…catch in sight. And no if-statements in our top-level function. If there’s a problem with any particular row, we just show an error message at the end. And note that in processRow() the only time we mention Left or Right is at the very start when we call right(). For the rest, we just use the .map() and .chain() methods to apply the next function.

Ap and lift

This is looking good, but there’s one final scenario that we need to consider. Sticking with the example, let’s take a look at how we might process the whole CSV data, rather than just each row. We’ll need a helper function or three:

https://medium.com/media/46091a8ef5f506c27973433ec0b40820/href

So, we have a helper function that splits the CSV data into rows. And we get an Either back. Now, we can use .map() and some lodash functions to split out the header row from data rows. But we end up in an interesting situation…

https://medium.com/media/887372191b27ceee29778d154cd1fdd3/href

We have our header fields and data rows all ready to map over with processRows(). But headerFields and dataRows are both wrapped up inside an Either. We need some way to convert processRows() to a function that works with Eithers. As a first step, we will curry processRows:

https://medium.com/media/fb5c29429951ea316ebc6f80f74f62b4/href

Now, with this in place, we can run an experiment. We have headerFields which is an Either wrapped around an array. What would happen if we were to take headerFields and call .map() on it with processRows()?

https://medium.com/media/7ed7dda23aaa2cc2d4fa945fa676812f/href

Using .map() here calls the outer function of processRows(), but not the inner one. In other words, processRows() returns a function. And because it’s .map(), we still get an Either back. So we end up with a function inside an Either. I gave it away a little with the variable name. funcInEither is an Either. It contains a function that takes an array of strings and returns an array of different strings. We need some way to take that function and call it with the value inside dataRows. To do that, we need to add one more method to our Left and Right classes. We’ll call it .ap() because the standard tells us to. The way to remember it is to recall that ap is short for ‘apply’. It helps us apply values to functions.

The method for the Left does nothing, as usual. And for the Right class, the variable name spells out that we expect the other Either to contain a function:

https://medium.com/media/f8168d658463585c9b081eeeee712cd6/href

So, with that in place, we can finish off our main function:

https://medium.com/media/3993a21f1cbb9d0ee9f9f95d0cf81870/href

Now, I’ve mentioned this before, but I find .ap() a little confusing to work with.² Another way to think about it is to say: “I have a function that would normally take two plain values. I want to turn it into a function that takes two Eithers”. Now that we have .ap(), we can write a function that will do exactly that. We’ll call it liftA2(), again because it’s a standard name. It takes a plain function expecting two arguments, and ‘lifts’ it to work with ‘Applicatives’. (Applicatives are things that have an .ap() method and an .of() method). So, liftA2() is short for ‘lift applicative, two parameters’.

So, liftA2() might look something like this:

https://medium.com/media/e6cbd8ef3ebfcb335fe58ace8afc917a/href

So, our top-level function would use it like this:

https://medium.com/media/26da1c3515222f9e4a073f765907cb57/href

You can see the whole thing in action on CodePen.

Really? Is that it?

Why is this any better than just throwing exceptions? Well, let’s think about why we like exceptions in the first place. If we didn’t have exceptions, we would have to write a lot of if-statements all over the place. We would be forever writing code along the lines of ‘if the last thing worked keep going, else handle the error’. And we would have to keep handling these errors all through our code. That makes it hard to follow what’s going on. Throwing exceptions allows us to jump out of the program flow when something goes wrong. So we don’t have to write all those if-statements. We can focus on the happy path.

But there’s a catch. Exceptions hide a little too much. When you throw an exception, you make handling the error some other function’s problem. But it’s all too easy to ignore the exception, and let it bubble all the way to the top of the program. The nice thing about Either is that it lets you jump out of the main program flow like you would with an exception. But it’s honest about it. You get either a Right or a Left. You can’t pretend that Lefts aren’t a possibility, eventually, you have to pull the value out with something like an either() call.

Now, I know that sounds like a pain. But take a look at the code we’ve written (not the Either classes, the functions that use them). There’s not a lot of exception handling code there. In fact, there’s almost none, except for the either() call at the end of csvToMessages() and processRow(). And that’s the point. With Either, you get pure error handling that you can’t accidentally forget. But without it stomping through your code and adding indentation everywhere.

This is not to say that you should never ever use try…catch. Sometimes that’s the right tool for the job, and that’s OK. But it’s not the only tool. Using Either gives us some advantages that try…catch can’t match. So, perhaps give Either a go sometime. Even if it’s tricky at first, I think you’ll get to like it. If you do give it a go though, please don’t use the implementation from this tutorial. Try one of the well-established libraries like Crocks, Sanctuary, Folktale or Monet. They’re better maintained. And I’ve papered over some things for the sake of simplicity here. And if you do give it a go, let me know by sending me a tweet.

Further reading

  1. Stroustrup, B., 2012, Bjarne Stroustrup’s C++ Glossary
  2. This is not helped by the fact that the Fantasyland specification defines .ap() in a confusing way. It uses the reverse order from the way most other languages define it.

Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.


Oldest comments (0)