Introducing Barefoot

August 09, 2013

by  Robert Anderson-Butterworth

Writing Asynchronous CoffeeSript with Barefoot

When writing large JavaScript and CoffeeScript applications, it does not take long before one encounters the awkward behemoth that is callback hell. Functions become longwinded, modules non-reusable and fairly soon code is so heavily indented that reading it comfortably requires a tilt of the head to the left.

Many libraries have been written to alleviate the pain associated with asynchronous programming. There is the async.js library, the excellent futures library and a whole ecosystem of projects based on the concept of using promises.

We at Creative Licence have begun trying a new approach to writing asynchronous CoffeeScript that focuses on writing very small, composable functions of a standardised form. barefoot is a small library written to assist with this style of programming and building web applications using it.

The problem

To demonstrate barefoot and its associated style of programming, let's introduce a small express.js web app that shortens URLs. Here it is:

express = require 'express'
Url = require './url' # mongoose model for storing urls

app = express()

app.post '/shorten', shorten
app.get  '/go/:hash', go

shorten = (req, res) ->
  if not req.body.url?
    return res.send 400

  Url.count (err, num) ->
    if err?
      return res.send 500

    url = new Url
      hash: num + 1
      location: req.body.url

    url.save (err, data) ->
      if err?
        res.send 500
      else
        res.send "/go/#{data.hash}"

go = (req, res) ->
  Url.findOne hash: req.params.hash, (err, data) ->
    if err?
      res.send 500
    else if data?
      res.redirect data.location
    else
      res.send 404

For simplicity, our hash is just an integer and we omit the mongoose code which defines our database object.

While this example is not too unbearable, we can see already that there is a lot of indentation and cruft that distracts the reader from quickly understanding the functionality of the code. In particular, the shorten method is unnecessarily long: 20 lines, with only 5 or 6 doing "interesting stuff".

Note also that the shorten method is already difficult to maintain. If we wanted to improve how we generate the hash and remove the call to Url.count, we must modify the entire later half of the method!

So what can we do to make this example look cleaner and easier to maintain?

Applying web abstractions

Firstly, let's introduce a wrapper that allows us to move away from the express.js convention of inspecting the req object and mutating the res object to handle requests. This will allow us to better compose functions together.

Instead, we'll write functions that take an object containing all of the request parameters we care about, and use the result of this function to determine our response to the web request. The function uses a callback to report the result of its asynchronous computation, or possibly an error that it had.

bf.webService allows us to use such a function with express.js, like so:

bf = require 'barefoot'
# ...
app.post '/shorten', bf.webService shorten
app.get  '/go/:hash', bf.webService go
shorten = (params, done) ->
  if not params.url?
    return done null, bf.badRequest()
  Url.count (err, num) ->
    if err? then return done err
    url = new Url
      hash: num + 1
      location: params.url
    url.save (err, data) ->
      if err? then return done err
      done null, "/go/#{data.hash}"
go = (params, done) ->
  Url.findOne hash: params.hash, (err, data) ->
    if err?
      done err
    else if data?
      done null, bf.redirect data.location
    else
      done null, bf.notFound()

Convenience methods such as bf.redirect are used to create results that tell bf.webService to perform a redirect. If an error is passed back through the callback, e.g. in the line if err? then return done err then barefoot will respond with an HTTP 500 message.

Handling errors

Now we can now simplify our example application.

One pattern that we see repeated is the checking for errors and passing them to the done callback. Let's avoid repeating this constantly by using barefoot's errorWrapper function to create a wrapper that does this error checking for us:

shorten = (params, done) ->
  w = bf.errorWrapper done
  if not params.url?
    return done null, bf.badRequest()
  Url.count w (num) ->
    url = new Url
      hash: num + 1
      location: params.url
    url.save w (data) ->
      done null, "/go/#{data.hash}"
go = (params, done) ->
  w = bf.errorWrapper done
  Url.findOne hash: params.hash, (data) ->
    if data?
      done null, bf.redirect data.location
    else
      done null, bf.notFound()

Much better! We are still correctly handling fatal errors, but no longer have cumbersome error handling code distracting us from the methods' actual functionality.

Breaking things up

Let's turn our attention towards breaking up these methods into smaller ones. Smaller functions which focus on doing one thing are easier to comprehend, as there is less for the reader to bear in mind. This helps bugs stand out more to the code reviewer, maintainer, or even yourself.

Additionally, smaller methods that are affected only by their arguments (pure functions) are easier to unit test, and, most importantly, easier to combine together and reuse.

barefoot's chain method takes an array of asynchronous (params, done) style methods, and creates a function which executes them in sequence, feeding the result of each function to the input of the next. If any of the chained functions has an error, the chain stops.

We can rewrite our shorten and go methods as bf.chain functions like so:

shorten = bf.chain -> [
  bf.validate
    url: String
  num: countUrls
  saveUrl
  bf.select (p) -> "/go/#{p.hash}"
]
countUrls = (params, done) ->
  Url.count done
saveUrl = ({ num, url }, done) ->
  url = new Url
    hash: num + 1
    location: url
  url.save done
go = bf.chain -> [
  getUrl
  doRedirect
]
getUrl = (params, done) ->
  Url.findOne hash: params.hash, done
doRedirect = (url, done) ->
  if url?
    done null, bf.redirect url.location
  else
    done null, bf.notFound()

Here you can also see some examples of convenience functions that barefoot exposes. bf.select allows simple functions to be chainable and bf.validate checks for the existence of required parameters and their type.

The num: countUrls syntax is a feature of bf.chain which stores the result of the asynchronous function, countUrls into the object being passed through the chain as a property called num.

Extending

Note now that our application has very reusable components. As a trivial example, if we wished to create a page that displayed the number of URLs that have been shortened, we could simply add:

app.get  '/stats', bf.webPage 'stats', stats
stats = bf.chain -> [
  count: countUrls
]

With the view page, stats.jade:

!!! 5
html
  body
    p There are #{count} URLs shortened!

Check it out

You can check out and contribute to barefoot on GitHub. The library is still in its very early days, and will change as we experiment with and refine these new techniques. Nonetheless, we hope it interests you!

Be sure to follow us on Twitter @Cre8iveLicence, and if this kind of stuff interests you, then you'll love working with us).