What’s the hell with callbacks?

Asked

Viewed 1,866 times

18

Going through some recent research, I came up with an unknown term for myself: "The callback Hell" or "The hell of the callbacks".

I was curious and decided to research a little, arriving at the following code snippet:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

Why is that bad?

How can I avoid?

What are the alternatives for not falling into this hell of callbacks?

  • I have no interest in answering, so I’ll just comment that this happens because people look for the most complicated ways. Interestingly the more experienced who could handle it better avoid abusing a resource that was made for thrifty use.

  • But then there’s no point in using callbacks?

  • 1

    This way I think not, if to change that is for the language is much simpler and has no problem at all. probably even the readdir would not need it, but if using it alone does not cause the reported problem. Of course it has advantage in specific cases, not abuse. Almost always a forEach() is a mistake.

  • 5

    "Almost always a foreach() is a mistake." You could explain that statement better?

2 answers

15


What is Callback Hell

Using your own code as an example. We can see that the callback hell is defined by this pyramid at the end of }). Simply awful.

Follow the passage:

            })
          }.bind(this))
        }
      })
    })
  }
})

How can I avoid?

The only way to avoid the hell of callbacks is by keeping good practice in your codes. Being these practices

1. Keep Code Clean and Easy

This is an example of a code that clearly is bad.

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

Let’s name the functions

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

Naming functions has immediate benefits such as:

  • Makes the code easy to read thanks to the description of the function in their names, above we have postResponse and formSubmit, which are self-explanatory.
  • When exceptions occur you will get more informative traces of where the problem is directly in the functions, rather than strangers "Anonymous".
  • Allows you to reference your functions.

Finally you can move the functions to the top of the program and will hardly have problems with the hell of the callbacks.

document.querySelector('form').onsubmit = formSubmit

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
}

2. Modularize

Anyone who makes a correct modularization of the code will hardly have trouble with the hell of the callbacks.

Creating a file called formuploader.js that contains our two functions previous, we can use the module.exports to modularize everything:

module.exports.submit = formSubmit

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
}

In Node.js we have the famous require, but we can also use this require in our browser with Browserify. So we have access to require, and can call the module we created in the file formuploader.js and then using the created module.

var formUploader = require('formuploader')
document.querySelector('form').onsubmit = formUploader.submit

And now we have two vital advantages. Being two of them:

  • Easy to read by new developers
  • formuploader can be used elsewhere.

3. Avoid any errors

Sure mistakes can happen, you need to make sure they don’t go through the code without you knowing where they are. Many callbacks are built with an argument that helps us deal with these errors.

For example:

var fs = require('fs')

fs.readFile('/Does/not/exist', handleFile)

function handleFile (error, file) {
     if (error) return console.error('Uhoh, there was an error', error)
     // otherwise, continue on and use `file` in your code
}  

note the error argument being treated in return console.error('Uhoh, there was an error', error) extremely important if any problem occurs.

More About Callback Hells or Callbacks Hell

4

Instead of callbacks, you can use promises ("Promise"). Nowadays you can already use Precedents with almost everything. They were created to relieve the callback Hell.

For example, you make an HTTP request to get basic information from a person. After that, take the person’s id and search for the address using another request. After the answer comes, you make another order, using the person’s address to pick up the purchase number or something. Follow the code:

encontrarPessoa()
  .then(response => {
    // Faz alguma coisa depois que pegar os dados básicos da pessoa.
    return pegarEndereco(response.id); // argumento: id da pessoa
  })
  .then(response => {
    // Faz alguma com o endereço obtido.
    return procurarCompras(response.completo); // endereço completo
  })
  .then(response => {
    // Faz alguma coisa com as compras
  })
  .catch(error => {
    // Tem que lidar com os errors aqui
    console.log('Deu problema!', error);
  });

One detail is that the functions that make the above HTTP request have to return a Promise, otherwise the business doesn’t work. The answer to find promise. If the promise is a success (no problem), the block then will be called. And then, when you return another promise from within a then, the result, if all goes well, goes to next block of then. And so on. If there’s a problem with any of the answers, the block catch is called and you can handle the error there.

  • Interesting. It is supported by most current browsers?

  • 1

    @Arturtrapp according to the documentation, Promise has been supported since ES6 ("Ecmascript 2015"). Usually one way or another, you can use it because of polyfill and third-party implementations. In the case of the server side, with Node.js, it has been supported for some time. See this Chart: http://node.green/

Browser other questions tagged

You are not signed in. Login or sign up in order to post.