barefoot is a library that assists with the creation of large asynchronous JavaScript applications, and, in particular, web applications using express.js.
It achieves this by encouraging the use of smaller, composable async functions with a particular form called bfunctions.
You can read more about the library in this blog entry.
app.get '/shortener/:hash', bf.webMethod(shortener)
shortener = bf.chain -> [
getUrl
redirect
]
getUrl = ({ hash }, done) ->
Url.findOne { hash }, done
redirect = (url, done) ->
if url?
done null, bf.redirect url.location
else
done null, bf.notFound()
npm install barefoot
To use it
bf = require 'barefoot'
A bfunction is a function of the form
fn(params: *, done: fn(err: *, res: *))
. That is, it is a function which takes
some parameters, performs some computation and then executes a callback function
with either the result of that computation or some error that occured.
When calling an asynchronous function, resulting errors must always be handled. barefoot provides several functions that make this less cumbersome.
We can use bf.errorWrapper
to avoid writing error handling code in our
callbacks. The error will be passed up one level.
getState = ({ id }, done) ->
User.findById id, (err, user) ->
if err? then return done err
Address.findById user?.addressId, (err, address) ->
if err? then return done err
done null, address?.state
Can be rewritten:
getState = ({ id }, done) ->
w = bf.errorWrapper done
User.findById id, w (user) ->
Address.findById user?.addressId, w (address) ->
done null, address?.state
To avoid heavily indented code ("callback hell"), we can use bf.sequence
to
flatten our method.
The example above can be rewritten, with bf.sequence
:
getState = ({ id }, done) ->
seq = bf.sequence done
seq.then (next) ->
User.findById id, next
seq.then (next, user) ->
Address.findById user?.addressId, next
seq.then (next, address) ->
next null, address?.state
seq.end()
Or, if you prefer being explicit and mutating some function-level state:
getState = ({ id }, done) ->
seq = bf.sequence done
[user, address] = []
seq.then (next) ->
User.findById id, seq.w (res) ->
user = res
next()
seq.then (next) ->
Address.findById user?.addressId, seq.w (res) ->
address = res
next()
seq.then (next) ->
done null, address?.state
bf.sequence
will end the sequence if an error is passed to next
.
Often a better approach to sequencing, however, is to break the method up into smaller, more manageable chunks and chain them together.
The example above can be rewritten, with bf.chain
:
getState = bf.chain -> [
getUser
getAddress
bf.get 'state'
]
getUser = ({ id }, done) ->
User.findById id, done
getAddress = (user, done) ->
Address.findById user?.addressId, done
bf.chain
will automatically handle errors and stop execution if one occurs,
propogating it up to the caller.
barefoot contains several bfunctions that are useful when chained, such as
bf.get
, used above.
Often it useful to to run two bfunctions concurrently in a chain.
bf.parallel
achieves this nicely:
getStateAndGroupName = bf.chain -> [
getUser
bf.parallel [
bf.chain [
getAddress
bf.get 'state'
]
bf.chain [
getGroup
bf.get 'name'
]
]
bf.select ([state, name]) -> "State: {state} Name: {name}"
getUser = ({ id }, done) ->
User.findById id, done
getAddress = (user, done) ->
Address.findById user?.addressId, done
getGroup = (user, done) ->
Group.findById user?.groupId, done
barefoot provides several methods that assists with the creation of web servers and web sites using bfunctions.
These methods take a bfunction and run it with a HTTP request object as the
input. The output is then coerced to a bf.HttpResponse
and this is used to
create an express.js callback function.
For example, this is a simple web application that echos the message given to it:
app.get '/echo/:message', bf.webService(echo)
echo = ({ message }, done) ->
done null, message
bf.webService
invokes the echo
method with all of the request's URL, query
and POST parameters. The result of echo
- a string - is coereced by
bf.webService
into a 200 OK response that has the string and nothing else.
The following method demonstrates a few more complicated features by moving the uploaded file 'image' and then redirecting to the page given in the X-Redirect header:
app.post '/foo', bf.webService(foo)
foo = (params, done) ->
w = bf.errorWrapper done
path = params.files.image.path
fs.rename path, "images/{path.basename(path)}", w ->
location = params.headers['X-Redirect'] ? '/'
done null, bf.redirect location
If an error occurs during the bfunction that bf.webService
executes, then a
HTTP 500 response is returned. If null is returned from the bfunction, then
HTTP 404 is returned.
bf.webPage
performs a similar task, except the result of the bfunction is
passed to a view template. The bfunction is also optional, allowing 'static'
pages.
app.get '/', bf.webPage('index')
app.get '/user/:id', bf.webPage('user', getUser)
getUser = ({ id }, done) ->
User.findById id, done
-
bf.chain
Composes bfunctions together, running each in sequence and giving the result of each function to the input of the next. If one bfunction in the chain has an error, the chain execution stops and the error is propogated up to the caller of the chain.
-
bf.parallel
Runs multiple bfunctions at the same time with the same input. An array of the results and errors is then returned.
-
bf.amap
-
bf.sequence
-
bf.avoid
Wraps a function which does not return anything and has no callback in a bfunction callable in a chain.
-
bf.select
Turns a regular function into a bfunction.
-
bf.get
Creates a bfunction that retrieves the given property from its parameters.
-
bf.map
Creates a bfunction that, given an array, produces a new array according to some mapping function.
-
bf.reduce
Creates a bfunction that, given an array, produces a single value given some reduction function.
-
bf.identity
bfunction that does nothing (returns input).
-
bf.HttpResponse
A class which encodes possibly HTTP responses applied to an express.js response.
-
bf.redirect
Convenience method that creates a
bf.HttpResponse
which redirects to the given url. -
bf.notFound
Convenience method that creates a
bf.HttpResponse
with HTTP code 404. -
bf.webService
Takes a bfunction and returns an express.js callback which executes the bfunction with the web request as input and response as output.
-
bf.webPage
Takes a bfunction and returns an express.js callback which executes the bfunction with the web request as input. The output of the bfunction is pased into the given view template.
-
bf.middleware
Takes a bfunction and reeturns an express.js middleware callback which runs the bfunction and aborts the express.js handler if there was an error.
-
bf.memoize
Takes a bfunction and a number of seconds and gives back a bfunction that has its output cached for the given number of seconds based on the input.
-
bf.validate
Takes a schema and gives back a bfunction that validates its input according to the schema, throwing a HTTP 400 (Bad Request) error if the params are invalid.
MIT
Written by Mathieu Guillout and Robert Anderson-Butterworth at Creative Licence Digital.