Holy $#!+, I hate callbacks.

Rants Sep 18, 2019

Header image from https://www.slideshare.net/HansGuntherSchmidt/promises-and-chaininginangularjs.

Let us make this clear. I F#&+ING HATE CALLBACKS. Not only are they ugly, they can create this monstrosity:

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));
          }
        });
      });
    }
});

(sourced from http://callbackhell.com/)

Look at that. It's ugly as hell, so much indentation, and just makes me exclaim why. This also makes it extremely difficult to develop on other developers' work. Now, lets look at the previous code but rewritten using the async/await pattern (using promises):

const files = await fs.readdir(source).catch(err => console.log('Error finding files: ' + err));

for (const filename of files) {
  console.log(filename);
  gm(source + filename).size(function (err, values) { // this cannot be avoided, the function scope is used later on
    if (err) {
      console.log('Error identifying file size: ' + err);
    } else {
      console.log(filename + ' : ' + values);
      const aspect = (values.width / values.height);
      
      for (const width of widths) {
        const height = Math.round(width / aspect);
        console.log('resizing ' + filename + ' to ' + height + 'x' + height);
        await this.resize(width, height).write(dest + 'w' + width + '_' + filename).catch(err => console.log('Error writing file: ' + err);
      }
    }
  });
}

Look at that. It's so much cleaner. Now, Node isn't the only language that suffers from this. Other languages do indeed suffer from callback hell. Any language that implements a callback style system will suffer from this. Lua, for example, is one that does this, especially in Roblox development. If you aren't familiar, Roblox uses its own event system which implements :connect(), a method that takes in another method when the event is fired. If not done correctly, you can get a similar sort of callback hell. Here's an example of what a Roblox event handler looks like:

game:GetService("Players").PlayerJoined:connect(function(ply)
    -- handle join code
end)

And now you can see how this can all come crashing down. Now unfortunately, it is much harder to implement promises into Lua, and either way Lua doesn't support async/await. But, if promises are used, we can do something like this:

game:GetService("Players").PlayerJoined:connect(function(ply)
    -- handle join code
    return someObject
end):then(function(someObject)
    -- another bit of async code
end):catch(function(err)
    -- catch-all error handler
end)

Not perfect, but you can sort of get the idea of what it would look like.

Anyway, that's my rant over. I've got to design graphics now.

LewisTehMinerz

Creative Director and Developer for OnePixel. Angry developer who rants about things. Likes anime I guess.