Using node.js async library reminds me of continuations and monads

I started playing around with Node.js in earnest, and have run into the dreaded nested callbacks that everyone complains about. 

I started off with something simple. It’s a http method that signs people up to a webpage. There are just a few steps:

  1. Use the submitted password to generate a crypted password
  2. Check to see if there’s a user already in the database with a certain facebook uid
    1. if there is, set the email and crypted password
    2. if there isn’t create a new user
  3. Save the user
  4. Set the user session

Not really hard. I’m pretty lazy, so I just put all three in a single gist. The progression is from a synchronous version, to nested callback, and then a async waterfall of the same code in coffeescript.

1
2
3
4
5
6
7
8
9
10
11
12
13
app.post '/users/signup', (req, res) ->
  crypted_password = User.generate_crypted_password req.body.user.password
  user = User.findOne { "facebook.uid": req.body.uid }
  if user
    user.email = req.body.user.email
    user.password = crypted_password
  else
    user = new User({ email: req.body.user.email, password: crypted_password })
  user.save
  req.session.regenerate

  req.flash 'info', "Welcome into the fold!"
  res.redirect 'hospitality/welcome'

The synchronous version of the code, you’d never write in node. This is because the calls to retrieve the User from the database blocks. 

Now, in the nested callback version is what you’d write in node. There’s a couple things to consider here. You can’t really throw errors in node, because by the time the callback is called, the stack that created the callback is already gone. So that means you need to

  1. handle the error in the callback itself
  2. otherwise pass errors from callback to callback.

All the error checking code, gets in the way of seeing what the good code path is. Also, it’s a bit of a pain when refactoring or reordering the business logic. Sure, there are emacs major modes (which is broken for coffeescript) and vim syntax highlighting autoindentations, but imo, if you have to rely on the IDE to help you write the language, that’s a weakness of the language that you’re making up for with the IDE. 

Some people say, suck it up, that’s the way things are suppose to be. Being from the Ruby world, but I can’t help but feel like nested callbacks are abusive. I found a blog post that recounted what various javascript gurus said.

  • Crockford himself, for example, thinks that this is just the way it should be, and people who don’t like it are just grumpy (Waa!)
  • Ryan Dahl glossed over the problem of losing the stack, and suggested that you just roll your own solution for remembering state across your series of function contexts.
  • Jed Schmidt revealed his fantastic hack: program in continuables/streams. The solution is actually very close to Haskell’s I/O Monad, and is both clever and beautiful, but it requires you to change your mind-set completely and therefore is unlikely to gain a lot of mindshare.
  • Tim Caswell introduced a helper library called Step which allows you to chain your functions rather than nest them.
  • Tom Hughes-Croucher recommended using named functions, for the stacktraces will display them names and therefore be more helpful.

via http://tobyho.com/Trampolines_in_Javascript_and_the_Quest_for_Fewer_Nested_Callbacks

I started looking at the list of node modules to see if there was something better. While I haven’t looked through all of them yet, async seemed promising, and one that a lot of people are using. 

In the aync.waterfall version, I was disappointed, it didn’t look any better. In fact, notice it looks very much like the synchronous code, but with all this chrome code around it. The chrome is used to wrangle the dataflow that one usually takes for granted in synchronous code. Notice what is required of you to do manually that’s implicit in synchronous code:

  1. Each “line” of code is actually a function that takes arguments that are the results from the previous “line” and a callback that you use to tell node the “next line” to execute. 
  2. The concept of scope across different “lines” goes out the window. You have to explicitly state what variables gets passed from “line” to “line”, since the scope of variables is only within a “line”.

The first point is like continuations. The second point is like a monad (a poor version of one), as explained by Overloading the semicolon. In fact, reading it again, I recognize some of the different monads he’s describing as different helper functions in the async library.

Or, you could replace the rule with “the first statement computes a list of values, and the second statement runs once using each of them“. This is the List monad; it’s — yes, you’re ahead of me here — it’s useful too.

That’s a little like async.map if its first argument was a callback that returned an array. 

It’s easier to understand what continuations and monads are for once you’ve run into problems it solves, rather than making analogies with spacesuits, burritos, etc. 

It’s like having to drive stick in flow control, when you’ve been driving automatic all your life. I think the control over flow control is necessary for an increasingly async/parallel/concurrent (I know they’re not all the same) world that we have to code for. However, I wish the syntax was much better. The proliferation of async libraries in node is indicative that a lot of people find the current javascript language constructs for flow control to be wanting, and are essentially inventing new language features. I wonder if it can’t be done much better. I’m not pleased with the solutions I’ve found so far. I’ll keep looking. The post on how JQuery is a monad is applicable here.

It may be that the easiest thing right now is to have named callbacks instead of anonymous callbaks to escape nested callback hell.

Advertisements

2 thoughts on “Using node.js async library reminds me of continuations and monads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s