Javascript Callback Hell and how I cope

I’m not a heavy Javascript coder, but I have already experienced “Callback Hell” more that I would like.

“Callback Hell” is well describe in this website, and they provide some ways to remedy. I however have some issues with this approach.

I’ve copied their example for “Keep your code shallow”, which I understand is the most generic solution (meaning the solution that doesn’t depend on external libraries).

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, postResponse)
}
function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}
document.querySelector('form').onsubmit = formSubmit

The issue that I have with this code is that there is no central function that gives me an outline of the program flow. For example, if you wanted to know how error messages are displayed, you would have to first go into formSubmit. There you notice that the callback for the request is postResponse so you read the postResponse function. There you finally find how the error messages are handled.

Having the program flow scattered all over the place makes my head hurt. I need to know how the functions in my code fit together. Especially if you are writing some controller code, you are most likely going to summon functions from a lot of different objects/classes and having to wade through those to understand program flow is something that I want to avoid. I want an overview of the program flow in one single location. I’ve tried “Keeping my code shallow”, but in my experience, it actually made the situation worse.

Coming from a Ruby background where the mantra is to keep methods short and clean, Javascript functions look awfully long and complicated. You feel an urge to separate these into discrete functions and this works if your code is synchronous. When you are dealing with asynchronous code however, you often find yourself writing the same kind of code described above, and you find that it’s more difficult to understand the refactored code than the initial monolithic function. This drove me crazy.

If you could write synchronous code, you could write something like this, which is how I would want to write it;

function formSubmit(submitEvent) {
  var name = document.querySelector('input').value
  response = request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  })
  postResponse(response)
}

The benefit of the synchronous code is that you now have a central location which dictates the flow of the code. You know upfront that your choices for hunting down the error handling code is either the request function or the postResponse function, and judging by the names, postResponse looks more likely. Compare this with the synchronous code where you only learn of the existence of the postResponse function after you read the call to request inside of formSubmit.

This becomes a huge problem when the code is more complex and you need to process the request through multiple steps (resulting in multiple nested callbacks).

My approach is to code like the following;

function formSubmit(submitEvent, callback) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, callback)
}
function postResponse(err, response, body) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
}
document.querySelector('form').onsubmit =
  function(event){formSubmit(event, 
    function(){postResponse(err,response,body)}
  )}

This style makes it clear that formSubmit subsequently calls postResponse.

If you need to do some more complex processing, you can write the following;

function formSubmit(submitEvent, callback) {
  var name = document.querySelector('input').value
  request({
    uri: "http://example.com/upload",
    body: name,
    method: "POST"
  }, callback())
}
function postResponse(err, response, body, callback) {
  var statusMessage = document.querySelector('.status')
  if (err) return statusMessage.value = err
  statusMessage.value = body
  callback()
}
function doSomethingExtra(callback){
  // Do something here
  callback();
}
function doEvenMore(callback){
  // Do something here
  callback();
}
document.querySelector('form').onsubmit =
  function(event){formSubmit(event, 
    function(err,response,body){postResponse(err,response,body,
      function(){doSomethingExtra(
        function(){doEvenMore()}
      )}
    )}
  )}

It is still super easy to understand the program flow. If you did something like this with the normal approach, you would have to first look into formSubmit, postResponse, doSomethingExtra and finally doEvenMore.

If you use Coffeescript, then the code would look like the following, which makes the intent of the code super easy to understand;

document.querySelector('form').onsubmit = (event) ->
  formSubmit event, (err, response, body) ->
    postResponse err, response, body, ->
      doSomethingExtra ->
        doEvenMore()

The interesting thing about the Coffeescript code is that with the functions statements out of the way, it starts to look like synchronous code with the exception that each consecutive method call is indented one step more than the previous. What I really like is that the program flow is as easy to understand as synchronous code.

Not being a heavy Javascript coder, I haven’t really put my head into error handling and such so I’m sure there is a lot that I’ve overlooked. I’m just posting here the way that I’m overcoming “Callback Hell”, and I really like it compared to the alternative remedies that I find on the web.