JavaScript Callback Functions

Learning Objectives
Students Will Be Able To: |
---|
Identify When a Function is a Callback Function |
Use Anonymous & Named Functions as Callbacks |
Use Callbacks with Array Iterator Methods |
Use the filter Array Iterator Method to "filter" an Array |
Use Callbacks with Asynchronous Operations |
Road Map
- Setup
- What's a Callback Function?
- Why are Callbacks Needed?
- Using Callbacks with Array Iterator Methods
- Practice Exercise - Using the
filter
Iterator Method - Using Callbacks with Asynchronous Functions
- Essential Questions
- Further Study
Videos
1. Setup
Create a new HTML/CSS/JS Repl in replit.com and name it Callback Functions.
2. What's a Callback Function?
A callback function is a function that's passed to another function as an argument.
function higherOrderFunction(callbackFunction) {
// Sooner or later...
callbackFunction();
}
A bit more vocabulary... A function that accepts a function as input and/or returns a function is known as a higher-order function.
Callback functions are not a new type of function, they are defined just like any other function (as a declaration, expression or arrow function).
Typically, the higher-order function accepts a callback for the purpose of invoking it - at least once, at some point in time.
Since you've previously used the array forEach
method, you've actually already passed a callback function...
colors.forEach(function(color, idx) {
...
});
Although using anonymous functions as callbacks is convenient, it's not uncommon to use a named function for code organization:
document.getElementById('todo-container')
.addEventListener('click', handleTodoClick);
Or when the callback might be called from more than just one point in the code:
/*-- Event Listeners --*/
document.getElementById('replay-btn').addEventListener('click', init);
/*-- Functions --*/
init();
function init() {
// Initialize state and call render
}
👀 Be careful not to invoke the callback when passing it, i.e., do not put parentheses after the function! Otherwise, you'll be passing the result returned by that function instead of the function itself.
Don't Do This:
document.getElementById('todo-container')
.addEventListener('click', handleTodoClick());
// No parens please ^^
❓ Review Questions - Callback Functions (1 min)
(1) What's a callback function?
A function that's passed as an argument to another function
(usually to be called by that function at least once, at some point)
const colors = ['red', 'green', 'blue'];
colors.forEach(function(color, idx) {
console.log(`${idx + 1} - ${color}`);
});
Refer to the above code when answering the next two questions...
(2A) What part of the code is the callback function?
👉function(color, idx) {
console.log(`${idx + 1} - ${color}`);
}👈
(2B) How many times will the higher-order function, forEach
, invoke the callback?
forEach
, invoke the callback?3
(once for each element in the array)
3. Why are Callbacks Needed?
There are two scenarios that require the use of callbacks:
-
Certain functions & methods - by design - need a block of code in order to perform their purpose. For example, both
forEach
&addEventListener
need a block of code to run. Callback functions provide that code. -
Certain higher-order functions/methods execute asynchronously in the background and are designed to invoke callback functions when they have finished their task. More on this later...
4. Using Callbacks with Array Iterator Methods
One of the most popular use-cases for callback functions is to provide them to iterator methods on arrays.
The forEach
method we've been using is a perfect example and has been designed to:
- Accept a callback function as an argument
- Invoke that callback once for each element in the array
5. 💪 Practice Exercise - Using the filter
Iterator Method (20 min)
-
Create a new HTML/CSS/JS Repl in replit.com and name it Filter Method Practice.
-
Copy and paste the following
cars
array into the repl:const cars = [
{ make: 'Toyota', yrsOld: 5, mileage: 92399 },
{ make: 'Ford', yrsOld: 12, mileage: 255005 },
{ make: 'Ferrari', yrsOld: 9, mileage: 12966 },
{ make: 'Subaru', yrsOld: 9, mileage: 111266 },
{ make: 'Toyota', yrsOld: 2, mileage: 41888 },
{ make: 'Tesla', yrsOld: 3, mileage: 57720 }
]; -
Research the array filter method.
👀 In summary, the
filter
method iterates over all of the elements in an array and returns a new array containing zero or more of the elements from that original array. The callback function determines which elements are included in the new array - if it returns a truthy value, the element for that current iteration will be included.
- Use the
filter
method to "select" the objects within thecars
array that have been driven more than 20,000 miles per year:
Hint: Like
forEach
, thefilter
method will invoke the callback once for each element in thecars
array. Note that: Each element is an object, so each time the callback is invoked, its parameter will hold an object withmake
,yrsOld
&mileage
properties.
-
Store the new array returned by
cars.filter(...)
in a variable namedwellDrivenCars
. -
You may use either an anonymous or a named function as the callback function provided to the
filter
method. -
Finally, use the
forEach
method on thewellDrivenCars
array toconsole.log
each "car" object.
We'll review a solution in 20 minutes...
5. Using Callbacks with Asynchronous Functions
JavaScript's asynchronous programming model is one of its more challenging concepts to learn, so don't despair if it takes a bit of time to understand.
Think of asynchronous programming as a way to allow multiple things to happen in a program at the same time...
Certain functions/methods are designed to be asynchronous because they take a long time to complete their task.
When an asynchronous function (or method) is called: it goes into the background to do its thing, allowing the remaining "synchronous" code to run to completion.
When the asynchronous operation has completed, it will call its callback function (or resolve its promise - yes....that's for another lesson 😉).
Consider the following code:
let sales;
console.log(sales); //-> undefined
// Assume getSalesData is an asynchronous function.
// When run, it will wait in the "background"...
// and call its callback function when the data arrives
getSalesData(function(salesFromDB) {
// Update sales variable when data arrives
sales = salesFromDB;
});
console.log(sales); //-> ???
❓ What will be logged out in the second console.log(sales)
above?
console.log(sales)
above?undefined
because getSalesData()
will wait in the background until the data from the database is returned, meanwhile the remaining synchronous code runs.
For a better understanding of asynchronous code, we'll start by confirming what synchronous code is...
Synchronous Code Execution
So far, we've taken for granted that the code we write runs line-by-line, so if we call a function/method it finishes before the next line of code runs (i.e., our code executes synchronously).
For example:
const colors = ['red', 'green', 'blue'];
console.log('Code BEFORE the forEach...');
colors.forEach(function(color, idx) {
console.log(`${idx + 1} - ${color}`);
});
console.log('Code AFTER the forEach...');
Running the above results in this expected output:
Code BEFORE the forEach...
1 - red
2 - green
3 - blue
Code AFTER the forEach...
Now let's see what asynchronous operations look like...
Asynchronous Code Execution
Until we start working with databases, etc., we can use the asynchronous setTimeout() & setInterval() functions which, when called, wait in the background until a specified amount of time elapses, then they call their callback functions.
Let's see setTimeout()
in action:
const colors = ['red', 'green', 'blue'];
console.log('Code BEFORE the forEach...');
// setTimeout accepts a callback & how long to wait before calling the cb
setTimeout(function() {
colors.forEach(function(color, idx) {
console.log(`${idx + 1} - ${color}`);
});
}, 1000); // 1000 milliseconds (1 sec)
console.log('Code AFTER the forEach...');
Running the above results in different output than the synchronous version:
Code BEFORE the forEach...
Code AFTER the forEach...
1 - red
2 - green
3 - blue
A Fun Asynchronous Example
The code below demonstrates how setTimeout
and callback functions can be used to implement the scenario where we need to:
- Do something...
- Wait for some period of time...
- Do something else...
- Wait for some period of time...
- etc.
🚦 Let's say we wanted a traffic light to cycle between Red, Green & Yellow lights with each light turned on for a certain amount of time:
- Red light on for 3 seconds
- Green light on for 2 seconds
- Yellow light on for 1 second
First, let's copy/paste the HTML into index.html:
<body>
<!-- add the following HTML -->
<main>
<div></div> <!-- red -->
<div></div> <!-- yellow -->
<div></div> <!-- green -->
</main>
Now for some CSS:
body {
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main {
display: grid;
grid-template-columns: 20vmin;
grid-template-rows: repeat(3, 20vmin);
border: 2px solid grey;
}
main div {
margin: 2vmin;
border-radius: 50%;
border: 2px solid grey;
}
REMINDER: Expect the following asynchronous code to be difficult to understand. However, the following example will come in handy if you choose to do a game like Simon or Concentration , a game that requires timing/sequencing.
For the JavaScript, let's start by defining a flexible data structure that makes it easy to modify the functionality (color and time):
// Using a "lookup" data-structure
// minimizes code and increases flexibility
const lightSequence = [
{color: 'red', time: 3000},
{color: 'green', time: 2000},
{color: 'yellow', time: 1000}
];
Next, cache the light <div>
s and define a variable to track the current light:
// Cache the divs for the lights
const lightEls = document.querySelectorAll('main > div');
// Variable to track the current light
let curLightIdx = 0; // Start with red object
Now, let's make the function responsible for displaying the current light AND for letting its caller know when the time for the light has expired:
function renderLight(cb) {
// First, turn off all lights
lightEls.forEach(el => el.style.backgroundColor = 'black');
// Next, turn on the current light
lightEls[curLightIdx].style.backgroundColor = lightSequence[curLightIdx].color;
// Invoke the callback when this light's time has expired
setTimeout(cb, lightSequence[curLightIdx].time);
}
Finally, another function that increments the curLightIdx
and calls renderLight
while passing itself as a callback:
function renderLightSequence() {
renderLight(renderLightSequence);
// Increment and reset to zero when 3 is reached
curLightIdx = ++curLightIdx % 3;
}
// Make it start!
renderLightSequence();
Very cool!
Let's wrap up with some questions!
❓ 6. Essential Questions (2 mins)
(1) If asked in a job interview, "What's a callback function?" - what would a good answer be?
A function that's passed as an argument to another function.
(2) Are callback functions are defined differently than non-callback functions?
No
(3) Is the following code likely to work as expected?
document.getElementById('items')
.addEventListener('click', handleClick());
document.getElementById('items')
.addEventListener('click', handleClick());
No, because the callback function, handleClick
, is accidentally being invoked.
(4) Is the addEventListener
method a higher-order function?
addEventListener
method a higher-order function?Yes, because it accepts a function as an argument. As a reminder, a function is also considered to be a higher-order function if it returns a function.
7. Further Study
The Browser's Event Loop
Here's a great video on how asynchronous operations - such as the handling of events - do their business and notify JavaScript when their work is done.
The Event Loop (in this video, the amazing and funny Jake Archibald from Google does an amazing job demonstrating the browser's event loop).
Event Loop & Queue Animation

There are browser "Web APIs" that keep track of events, setTimeouts, etc.
For example, when we call setTimeout()
the browser keeps track of the timing. When the time expires, it will place the setTimeout's callback function into the callback/event queue to be executed as soon as there are no other functions running in the call stack.