Everything About Callback Functions in JavaScript
The callback function is one of those concepts that every JavaScript developer should know. Callbacks are used in arrays, timer functions, promises, event handlers, and much more.
In this post, I will explain the concept of a callback function. Also, Iâll help you distinguish the 2 types of callbacks: synchronous and asynchronous.
1. The callback function
How can you compose a message to greet a person?
Letâs create a function greet(name)
that accepts a name
argument. The function should return the greeting message:
function greet(name) {
return `Hello, ${name}!`;
}
greet('Cristina'); // => 'Hello, Cristina!'
What about greeting a list of persons? Thatâs possible using a special array method array.map()
:
const persons = ['Cristina', 'Ana'];
const messages = persons.map(greet);
messages; // => ['Hello, Cristina!', 'Hello, Ana!']
persons.map(greet)
takes each item of the persons
array, and invokes the function greet()
using each item as an invocation argument: greet('Cristina')
, greet('Ana')
.
Whatâs interesting is that persons.map(greet)
method accepts greet()
function as an argument. Doing so makes the greet()
a callback function.
The persons.map(greet)
is a function that accepts another function as an argument, so it is named a higher-order function.
The callback function is supplied as an argument to a higher-order function that invokes (âcalls backâ) the callback function to perform an operation.
Whatâs important is that the higher-order function takes the full responsibility of invoking the callback and supplying it with the right arguments.
In the previous example, the higher-order function persons.map(greet)
takes the responsibility to invoke the greet()
callback function with each item of the array as an argument: 'Cristina'
and 'Ana'
.
That brings to an easy rule for identifying callbacks. If youâve defined a function and youâre not invoking it by yourself â but rather supply as an argument to another function â then youâve created a callback.
You can always write by yourself higher-order functions that use callbacks. For example, hereâs an equivalent version the array.map()
method:
function map(array, callback) {
const mappedArray = [];
for (const item of array) {
mappedArray.push(
callback(item) );
}
return mappedArray;
}
function greet(name) {
return `Hello, ${name}!`;
}
const persons = ['Cristina', 'Ana'];
const messages = map(persons, greet);messages; // => ['Hello, Cristina!', 'Hello, Ana!']
map(array, callback)
is a higher-order function since it accepts a callback function as an argument, and then inside of its body invokes that callback function: callback(item)
.
Note that a regular function (defined using function
keyword) or an arrow function (defined using the fat arrow =>
) can equally serve as callbacks.
2. The synchronous callback
There are 2 types of callbacks by the way theyâre invoked: synchronous and asynchronous callbacks.
The synchronous callback is executed during the execution of the higher-order function that uses the callback.
In other words, the synchronous callbacks are blocking: the higher-order function doesnât complete its execution until the callback is done executing.
For example, recall the map()
and greet()
functions.
function map(array, callback) {
console.log('map() starts');
const mappedArray = [];
for (const item of array) { mappedArray.push(callback(item)) }
console.log('map() completed');
return mappedArray;
}
function greet(name) {
console.log('greet() called');
return `Hello, ${name}!`;
}
const persons = ['Cristina'];
map(persons, greet);
// logs 'map() starts'
// logs 'greet() called'
// logs 'map() completed'
greet()
is a synchronous callback because itâs being executed at the same time as the higher-order function map()
. You can try the demo.
The synchronous way to invoke the callbacks:
- The higher-order function starts execution:
'map() starts'
- The callback function executes:
'greet() called'
- Finally, the higher-order function completes its execution:
'map() completed'
2.1 Examples of synchronous callbacks
A lot of methods of native JavaScript types use synchronous callbacks.
The most used ones are the array methods like array.map(callback)
, array.forEach(callback)
, array.find(callback)
, array.filter(callback)
, array.reduce(callback, init)
:
// Examples of synchronous callbacks on arrays
const persons = ['Ana', 'Elena'];
persons.forEach(
function callback(name) { console.log(name);
}
);
// logs 'Ana'
// logs 'Elena'
const nameStartingA = persons.find(
function callback(name) { return name[0].toLowerCase() === 'a';
}
);
nameStartingA; // => 'Ana'
const countStartingA = persons.reduce(
function callback(count, name) { const startsA = name[0].toLowerCase() === 'a';
return startsA ? count + 1 : count;
},
0
);
countStartingA; // => 1
string.replace(callback)
method of the string type also accepts a callback that is executed synchronously:
// Examples of synchronous callbacks on strings
const person = 'Cristina';
// Replace 'i' with '1'
person.replace(/./g,
function(char) { return char.toLowerCase() === 'i' ? '1' : char;
}
); // => 'Cr1st1na'
3. The asynchronous callback
The asynchronous callback is executed after the execution of the higher-order function.
Simply saying, the asynchronous callbacks are non-blocking: the higher-order function completes its execution without waiting for the callback. The higher-order function makes sure to execute the callback later on a certain event.
In the following example, the later()
function is executed with a delay of 2 seconds:
console.log('setTimeout() starts');
setTimeout(function later() {
console.log('later() called');
}, 2000);
console.log('setTimeout() completed');
// logs 'setTimeout() starts'
// logs 'setTimeout() completed'
// logs 'later() called' (after 2 seconds)
later()
is an asynchornous callback because setTimeout(later, 2000)
starts and completes its execution, but later()
is executed after passing 2 seconds. Try the demo.
The asynchronous way to invoke the callbacks:
- The higher-order function starts execution:
'setTimeout() starts'
- The higher-order function completes its execution:
'setTimeout() completed'
- The callback function executes after 2 seconds:
'later() called'
3.1 Examples of asynchronous callbacks
The timer functions invoke the callbacks asynchronously:
setTimeout(function later() {
console.log('2 seconds have passed!');
}, 2000);
// After 2 seconds logs '2 seconds have passed!'
setInterval(function repeat() {
console.log('Every 2 seconds');
}, 2000);
// Each 2 seconds logs 'Every 2 seconds!'
DOM event listeners also invoke the event handler function (a subtype of callback functions) asynchronously:
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', function handler() {
console.log('Button clicked!');
});
// Logs 'Button clicked!' when the button is clicked
4. Asynchronous callback function vs asynchronous function
The special keyword async
placed before the function definition creates an asynchornous function:
async function fetchUserNames() {
const resp = await fetch('https://api.github.com/users?per_page=5');
const users = await resp.json();
const names = users.map(({ login }) => login);
console.log(names);
}
fetchUserNames()
is asynchronous since itâs prefixed with async
. The function fetches await fetch('https://api.github.com/users?per_page=5')
first 5 users from GitHub. Then extracts from the response object the JSON data: await resp.json()
.
The asynchronous functions are syntactic sugar on top of promises. When encountering the expression await <promise>
(note that calling fetch()
returns a promise), the asynchronous function pauses its execution until the promise is resolved.
An asynchronous callback function and an asynchronous function are different terms.
The asynchronous callback function is executed in a non-blocking manner by the higher-order function. But the asynchronous function pauses its execution while waiting for promises (await <promise>
) to resolve.
However⊠you can use an asynchronous function as an asynchronous callback!
Letâs make the asynchornous function fetchUserNames()
an asynchronous callback called on button click:
const button = document.getElementById('fetchUsersButton');
button.addEventListener('click', fetchUserNames);
Open the demo and click Fetch Users. When the request completes, youâll see a list of users logged to the console.
5. Summary
The callback is a function thatâs accepted as an argument and executed by another function (the higher-order function).
There are 2 kinds of callback functions: synchronous and asynchronous.
The synchronous callbacks are executed at the same time as the higher-order function that uses the callback. Synchronous callbacks are blocking.
On the other side, the asynchronous callbacks are executed at a later time than the higher-order function. Asynchronous callbacks are non-blocking.
Quiz: does setTimeout(callback, 0)
execute the callback
synchronously or asynchronously?
Quality posts into your inbox
I regularly publish posts containing:
- Important JavaScript concepts explained in simple words
- Overview of new JavaScript features
- How to use TypeScript and typing
- Software design and good coding practices
Subscribe to my newsletter to get them right into your inbox.