What is the hell of the callbacks?

12

Going through some recent research, I came up with an unfamiliar term for myself: "The callback hell."

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 this bad?

How can I avoid it?

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

    
asked by anonymous 08.03.2017 / 14:11

2 answers

12

What is Callback Inferno

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

Here's the snippet:

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

How can I avoid it?

The only way to avoid the hell of callbacks is to keep good practice on your codes. Being these practices

1. Keep Code Clean and Easy

This is an example of a code that is clearly 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 roles has immediate benefits like:

  • Make the code easy to read thanks to a 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 lies directly in functions rather than strangers "anonymous".
  • Allows you to reference their functions.

Finally you can move the functions to the top of the program and you 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 does a correct code modularization will hardly have a problem with the hell of callbacks .

Creating a file named formuploader.js that contains our previous functions , we can use 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, however we can also use this require in our browser with browserify . So we have access to require, so we 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. Two of them are:

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

3. Avoid errors

Surely mistakes can happen, you need to take care that they do not 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
}  

Notice the error argument being treated in return console.error('Uhoh, there was an error', error) extremely important in case of a problem.

More About Callbacks Hells or Hell

    
08.03.2017 / 16:08
4

Instead of callbacks, you can use promises (" promise "). Nowadays you can use promises with almost everything. They were created to relieve the callback hell.

For example, you make an HTTP request to pick up a person's basic information. After that, it takes the id of the person and searches for the address using another request. After the answer comes, you make another request, using the person's address to pick up the number of purchases or something. Here is 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 HTTP request above have to return an Promise , otherwise the deal will not work. The answer to findPerformance () returns a promise . If the promise is a success (no problem), the then block will be called. And then, when you return another promise from within a then block, the result, if all goes well, goes to next . And so on. If you get a problem with any of the answers, the catch block is called and you can deal with the error there.

    
08.03.2017 / 21:27