Photo by Max Nelson on Unsplash

I recently completed week 7 at Flatiron School NYC, and I can easily say that this was my most challenging week yet. This week we were introduced to JavaScript, leaving our Ruby and Rails days behind (for the moment). Despite having studied basic JavaScript during some pre-bootcamp work, I felt as though I was starting from scratch… and attempting to learn ancient sanskrit. Transitioning from a clean, organized and happy language like Ruby to vanilla JavaScript was overwhelming. I found myself staring at lines of code filled with parenthesis and curly brackets, missing the order and “simplicity” of Ruby.

However, one thing was clear about this messy, seemingly disorganized language: it is extremely powerful.

In an effort to solidify some of the more challenging concepts (at least for me) of JavaScript in my mind, and hopefully help clarify for others who are new to coding, I decided to delve a bit deeper into the concept of Callback functions.

What is a Callback Function?

In simple terms, a Callback function is a function that will be executed after another function has completed. Ok, that sort of makes sense. That’s basically what the word “callback” sounds like, but, um, WHAT?

The first step in understanding callback functions is understanding that in JavaScript a function is actually an Object, and this means that you can operate on a function in all the same ways that you can operate on an Object. For example, I could do something like this:

function testFunc( ) {   
return "I am here"
}
// undefined
testFunc["foo"] = "bar"
// "bar"
testFunc["foo"]
// bar
testFunc.foo
// bar
testFunc( )
// "I am here"

Above, I am defining a function, testFunc( ), and am simply setting its return value to the string "I am here". If I then decide to create a key foo on my testFunc( ) Object and set it equal to "bar", I can. If I access testFunc at this key (either using bracket notation or dot notation), I get bar returned. If I call my function, testFunc( ), it will still return my string "I am here".

Ok, so we can clearly see that functions are Objects and we can operate on them exactly as we’d operate on Objects. If this is the case, then we should be able to pass a function into another function as an argument, right? We can do this with Objects, so why not functions? (spoiler alert, we can). Conversely, we can also have the return of a function be another function.

Functions that return other functions are called “higher-order” functions, while a function that is passed as in an argument is a callback function.

Why Do We Use Callbacks?

So we can move on with our lives… errr, code.

Let Me Explain

At its base, JavaScript is a synchronous language. This means that it only runs one operation at a time — left to right, from top to bottom. If we call two functions, JavaScript will first run function “A” (i.e. the function we invoke first) and will ONLY run function “B” when, and if, function “A” completes.

Let’s define two functions and test this out.

function a( ){ 
console.log("I'm first")
}
function b( ){
console.log("I'm second")
}
a( )
b( )

This would console.log pretty much how we would expect:

// I'm first 
// I'm second

But what if function “A” contained something that takes longer to resolve, such as heavy logic or math, or a database request. What if it never resolved?! We’d be waiting forever for function “B” to run, but function “B” may never run. So sad 😢.

Aside from us feeling sad, It’s also, like, the worst user experience ever. We don’t want to make our users wait an indeterminate amount of time for our website to load.

In order to mitigate this issue, we are able to manipulate JavaScript in a number of ways so that it behaves in an asynchronous manner. Callback functions are one of those ways. They allow us to set aside certain code to execute only after other code has finished.

Great, now we can move on with our code… and lives.

Basic Example

As we defined above, a callback function is one that is passed in as an argument to another function. That seems pretty clear, so let’s create a simple example. I’ll start by defining the basic makeLunch( ) function below. This takes in one argument of food and returns a string.

function makeLunch(food) { 
console.log(`I'm making ${food} for lunch`)
}
makeLunch("PB&J")
// I'm making PB&J for lunch

Now let’s add a callback to our function. We can now invoke our function with two arguments, the first will still be our lunch food, and the second will actually be a function. We can type this in directly, as so:

function makeLunch(food, callBack) { 
console.log(`I'm making ${food} for lunch`)
callBack( )
}
makeLunch("PB&J", function( ) {
console.log("I ate my lunch!")
})
// I'm making PB&J for lunch
// I ate my lunch!

When we invoke our makeLunch( ) function with "PB&J" as our first argument and a callback function as our second, we'll actually get both console.log's back. In essence, we invoked the first function, makeLunch( ), which then invoked the second function.

We can also refactor this code and define our callBack function as an entirely separate function. We can then simply pass it in as our second argument.

function makeLunch(food, callBack) { 
console.log(`I'm making ${food} for lunch`)
callBack( )
}
function eatLunch( ) {
console.log("I ate my lunch!")
}
makeLunch("PB&J", eatLunch)
// I'm making PB&J for lunch
// I ate my lunch!

This will give us the exact same return, but by writing it in this manner we are able to store our callBack as its own function, allowing us to use it in other places if needed.

Another Example

Certain functions in JavaScript act asynchronously (you can learn more about that here) and these often take callbacks as arguments. Let’s look at setTimeout( ) as an example. The setTimeout( ) method accepts a callback function or an expression and invokes/evaluates it after a specified number of milliseconds. We could write it like so:

setTimeout(callBack, 2000)

Let’s define two functions; a callback function that we will pass into to our setTimeout( ) function and a second function which we will simply invoke on its own, after invoking setTimeout( ).

function waiting( ) { 
console.log("I'm going as fast as I can!")
}
function here( ) {
console.log("Just sitting here twiddling my thumbs")
}
setTimeout(waiting, 2000)
here( )
// Just sitting here twiddling my thumbs.
// I'm going as fast as I can!

Whoaw! What happened? Even though we invoked setTimeout( ) before invoking here ( ), we got our console.log of "Just sitting here twiddling my thumbs" before "I'm going as fast as I can!". This is because setTimeout( ) is an "asynchronous" function and waited 2 seconds before invoking our callbacks function.

This example makes our use of callbacks and their benefits a bit clearer. Callbacks allow us to wait for something to complete — in this case it was a simple 2 second wait, but in other cases it could be a server response — before invoking a function that may depend on this response.

Closures

Since callback functions are executed inside of a containing function, they also have access to the containing function’s scope, making it a closure. This means that callback functions have access to its parent function’s variable as well as global variables. For example:

function makeLunch(food, callBack) { 
console.log(`I'm making ${food} for lunch`)
callBack(food)
}
function eatLunch(lunch) {
console.log(`I ate my ${lunch}!`)
}
makeLunch("PB&J", eatLunch) // I'm making PB&J for lunch
// I ate my PB&J!

Or

function makeLunch(callBack) { 
const food = "PB&J"
console.log(`I'm making ${food} for lunch`)
callBack(food)
}
function eatLunch(lunch) {
console.log(`I ate my ${lunch}!`)
}
makeLunch(eatLunch) // I'm making PB&J for lunch
// I ate my PB&J!

If this is a bit confusing, let’s think back to earlier when we demonstrated two ways of writing a callback function. In the first example, we actually defined our function right inside our parent function. Looking at it this way may make it a bit clearer as to how we have access to our enclosing function’s variables.

Wrapping It Up

Callbacks functions are a great JavaScrip tool for executing our code in specified locations and at specified times. Of course, we merely scratched the surface on their usage and functionality, but hopefully we are all able to better understand when, how and why we may use a callback function in our code.

Sources:

Full-stack web developer with a background in marketing and graphic design. A coding journey: From nil to skill

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store