By Matt MerkesMay 10, 2014

What is a callback, anyway?

Back in my early days of noobhood, I started taking a look at Node.js and quickly got overwhelmed with all of these strange things called callbacks.

And if you’ve ever written a Node app, you know that they are full of callbacks, and I just couldn’t wrap my head around what in the world a callback was.

What is a callback, anyway?

In computer programming, a callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at some convenient time. The invocation may be immediate as in a synchronous callback or it might happen at later time, as in an asynchronous callback.

Wikipedia actually does a fairly sensical job of defining callbacks. In other words, a callback is a function that gets passed into another function as an argument where it’s executed when the parent function wants it to be executed.

Imagine that you’re an expert soup maker and claim to be able to make soup out of any vegetables someone wants. A bold statement, but I trust in your cooking skills for now. Along with the vegetables, you want to know how they want the vegetables cut: sliced, diced, chopped, or minced. With that information, you go to work and make soup. You don’t ask when they want them chopped as you already know when they need to be chopped. Chopping, slicing, and dicing are the possible callbacks. How would that look in code?

// veggies being an array
function cook_soup( veggies, callback ) {
    var soup = '';

    // Go through each veggie and pass it to the callback function
    for( var i = 0; i < veggies.length; i++ ) {
        soup = soup + callback( veggies[i] ) + ', ';
    }

    soup = soup + 'and the secret ingredients';

    console.log(soup);
}

// create the dice function
function dice( veggie ) {
    return 'diced-' + veggie;
}

// Let's make some soup!
cook_soup([ 'potatoes', 'onions', 'tomatoes', 'garlic'], dice);
// logs: 'diced-potatoes, diced-onions, diced-tomatoes, diced-garlic, and the secret ingredients'

As you can see, we passed the dice function into the cook_soup function, which knew exactly how to handle the dicing. You can also pass an anonymous function (a function without a name) in as the callback:

// Let's make soup with an anonymous callback!
cook_soup([ 'onions', 'garlic', 'tomatoes', 'jalapeno' ], function( ingredient ) {
    // Notice how the ingredient variable named in this anonymous
    // callback didn't need to be called veggie? Call it whatever
    // you like as long as it handles what's getting passed into it.
    return 'chopped-up-good-' + ingredient;
});
// logs: 'chopped-up-good-onions, chopped-up-good-garlic, chopped-up-good-tomatoes, chopped-up-good-jalapeno, and the secret ingredients'

You can really pass any function into cook_soup as long as you know how it will be handled and don’t give it something wacky like:

function dice_2_veggies( veg1, veg2 ) {
    return 'diced-' + veg1 + ' and ' + 'diced-' + veg2;
}
// This would return 'diced-veggie and diced-undefined' since 'cook_soup' only passes
// it one veggie and the function wants 2.
Depending on the function that you pass in as an argument, you might get unexpected behavior if you don't craft it in the way it's expected.

Now, why do we use callbacks? Because they allow us to reuse our code, and they can become extremely powerful when used properly. When it comes to asynchronous programming (basically, allowing other operations to continue before one operation has completed - i.e. waiting for data in the database), callbacks are essential because you want to tell a function what to do once it’s done with a task.

Let’s look at handling jQuery events. If we look at the click method, which listens for click events on a particular element(s), the basic definition is as such:

.click( handler(eventObject) )

The handler mentioned here is really just an asynchronous callback. It’s saying that if a click event is detected in the specified element(s), perform the function passed in as a handler. For example, if you open up the console in your browser and paste this code:

$('.profile').click( function() { 
    console.log("Stop poking me!"); 
});

Every time you click on the profile picture on the right hand side of the page, it will log “Stop poking me!” to the console. In this case, it’s listening for clicks on the profile picture and performing the callback each time you click.

Node.js uses callbacks ALL THE TIME. I’m not going to get into it in this post, but Node has the convention of using callbacks that take an error and a second argument that is typically whatever the parent function returns (i.e. data).

// Include file system node module.
var fs = require('fs');

// Read veggies.js and once you've read it, throw an error if there is one.
// Otherwise, convert the buffer to a string and log the file to console
fs.readFile('veggies.js', function(error, data) {
    if(error) throw error;

    console.log(data.toString());
});
// logs: The exact text included in veggies.js!

If you’re not familiar with Node, you may be wondering: Why not just save the result of the readFile call into a variable and log it to the console? Doesn’t a callback just make this more complicated?

var content = fs.readFile('veggies.js');
console.log(content);
// This doesn't work!

Because Node is asynchronous (things don’t necessarily happen in the order you type them into your file), readFile would be called, but it wouldn’t return the contents until after console.log printed content, which would be undefined at this point since it was declared, but it’s still waiting for a value. In this example, callbacks are handy because we can be sure that the contents will not be logged to the console until readFile fires the callback once it’s good and ready!

That’s a callback in a nutshell. I hope your noob brain isn’t hurting too much. Callbacks can take a long time to really master as they can be complicated, especially Node callbacks. Hang in there!

Just remember: Callbacks are your friends!

In future posts, I plan on covering the ins and outs of callbacks in more detail. For example, how to create Node callbacks in a Node-way: callback (error, data)

Stay tuned!

Check out the original post and more resources on Matt’s blog, Noob.js.