05. Better Error Handling with Function Composition // JavaScript Codecasts
Articles Blog

05. Better Error Handling with Function Composition // JavaScript Codecasts

October 28, 2019


How do you handle runtime errors without a
mess of try…catch and if…else statements? Let’s see how higher-order functions and
composition can help on today’s episode of TL;DR, the JavaScript codecast series that
teaches working web developers to craft exceptional software in 5 minutes a week. Last episode we saw how Custom Errors can
often make our code worse, but Custom Exceptions can help by allowing intermediary functions
to focus only on the feature’s happy path. If you’re just now joining us, hop back
to the previous episode on Custom Exceptions. Exceptions are useful when they eliminate
if…else statements from calling functions, but at some point an Exception needs to be
caught and handled, and that’s where the try…catch statement tends to make a mess
of things. Today we’re continuing to refactor a tiny
chatbot we started a few episodes ago that helps outdoor enthusiasts find great trails
to hike. Like last time, our chatbot only understands
one command, “view hike”. Most of the time this command replies with
details about the hike, but when users ask for a hike that isn’t in the database or
their syntax is a bit off, the viewHike() function will throw a custom exception like
a NotFound error or a ValidationError. In either case, the chatbot shouldn’t blow
up and stop running, so we started by wrapping a try…catch statement around the problematic
code. But we quickly realized that every use of
try…catch takes a substantial amount of boilerplate to keep from introducing a catch-all
bug. To make sure we only rescued a particular
error type, we introduced a simple utility called rescue(): a guard clause which rethrows
the error if the type differs from what we intended to catch. The problem with rescue() is that it only
helps us catch one type of error at a time. So how do we handle both a NotFound error
and ValidationError? We could make the rescue() function accept
multiple error types, but then we couldn’t customize the fallback message based on the
error type. So do we have to give up the rescue() utility
altogether and use cascading if…else statements to uniquely handle different error types? Maybe not if we factor a little further. Our remaining try…catch boilerplate is starting
to turn into an obvious pattern: if we were to reuse this try…catch in another part
of the codebase, all that changes is the function to invoke, what type of error to rescue, and
what to return if there is an error. Let’s extract this formula into a function
called swallow(), which takes the error type to swallow, a fallback function, and a function
that will potentially throw an error. Now we’ll use swallow() to create a new
version of viewHike() that is safe from NotFound errors. It seems to work as before! But this code is still pretty verbose, and
some might argue it’s more cryptic than simply writing a try…catch with cascading
if…else statements. Well, if we just change the signature of swallow()
a bit to take advantage of currying, we can eliminate a lot of the extra function calls
and argument gathering. Whoah, look at swallow() now! It’s a Higher-Order Function: it takes in
an unsafe function that throws a particular kind of error, and returns a safe version
of the function. Because swallow() returns a function that
is safe from the NotFound error type, there’s no reason we can’t pass that function into
swallow() again to make it safe from a ValidationError too! That nesting is a bit nasty, but this is just
the sort of thing the compose() utility is for: instead of nesting swallow()s inside
each other, we can list them out from top to bottom and feed the original viewHike()
function at the very end. It works exactly the same way as manually
feeding the results of each swallow() into the other, but it’s much easier to read
and maintain. This style of creating functions without first
gathering and passing around all their arguments is called Point-free style, and it’s a big
part of what makes functional programming so elegant. It took us some time to arrive at this design,
and many of the intermediate steps seemed a lot worse off than just using try…catch. But just like the Enforcer pattern we covered
in an earlier episode, the best way to combine behaviors is through composition. Rather than cascading if-else statements,
complex multiple error handling logic, or experimental catch syntax, we handled two
kinds of errors through composition. If you aren’t already in love with function
composition, hang tight until the next episode: we’ll use error composition to put a functional
twist on a popular Object-Oriented Programming pattern called the Null Object Pattern. Today, look for try…catch statements in
your codebase, and break down the parent function until you can replace the try…catch altogether
with swallow(). And if you need to handle multiple error types,
just layer them with compose(). That’s it for today, you can get a transcript
of today’s episode and catch up on other ways to craft exceptional code at JonathanLeeMartin.com/TLDR,
and if you want to keep leveling up your craft, don’t forget to subscribe to the channel
for more rapid codecasts on design patterns, refactoring and development approaches.

Only registered users can comment.

  1. Hi Jonathan, could you please explain everything a bit slower and with more detail ? It's really hard to keep up and understand what's going on 😓

  2. Hi Jonathan, this video series is awesome and super inspiring! 🙂 And I hope I’m not mistaken in thinking it will dig deeper and deeper into functional programming concepts the further we get in to it. I wish I could see the entire code, after each episode, instead of just the transcript. And as another feature request, I really hope you get in to the functional approach to http requests using I/O monads or something similar. Keep up the great work! This series really deserves more attention! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *