JavaScript course menu
JavaScript Callbacks and Higher-Order Functions
Learn callbacks and higher-order functions in JavaScript: passing functions as arguments, returning functions, writing your own higher-order functions, synchronous and asynchronous callbacks, callback hell, and composing small functions.
On this page
A callback is a function you pass to another function, to be called later. A higher-order function is a function that takes a function as an argument or returns one. Together they are one of the most common patterns in JavaScript.
Passing a function#
function repeat(times, action) {
for (let i = 1; i <= times; i++) {
action(i);
}
}
repeat(3, (n) => console.log(`Push-up ${n}`));Push-up 1
Push-up 2
Push-up 3repeat does not know what to do on each step; the caller decides by passing action. That makes repeat reusable for anything.
Built-in higher-order functions#
You will use these all the time. Each takes a callback:
const scores = [72, 95, 58, 88];
scores.forEach((s, i) => console.log(`Student ${i + 1}: ${s}`));
console.log(scores.filter((s) => s >= 70));
console.log(scores.map((s) => s + 5));
console.log([...scores].sort((a, b) => b - a));Student 1: 72
Student 2: 95
Student 3: 58
Student 4: 88
[ 72, 95, 88 ]
[ 77, 100, 63, 93 ]
[ 95, 88, 72, 58 ]They are covered in detail in Array Methods.
Writing your own#
Writing a simple version of a built-in method shows how callbacks work:
function myFilter(array, test) {
const result = [];
for (const item of array) {
if (test(item)) result.push(item);
}
return result;
}
const words = ["sun", "planet", "galaxy", "moon"];
console.log(myFilter(words, (w) => w.length > 4));
console.log(myFilter([1, 2, 3, 4, 5, 6], (n) => n % 2 === 0));[ 'planet', 'galaxy' ]
[ 2, 4, 6 ]Returning functions#
A higher-order function can also return a function, often using a closure:
const greaterThan = (limit) => (n) => n > limit;
const prices = [99, 250, 499, 1200];
console.log(prices.filter(greaterThan(200)));
console.log(prices.filter(greaterThan(1000)));[ 250, 499, 1200 ]
[ 1200 ]Composing functions#
Small functions can be combined into bigger ones:
const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value);
const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const slugify = (s) => s.replaceAll(" ", "-");
const toSlug = pipe(trim, lower, slugify);
console.log(toSlug(" Learn JavaScript Today "));learn-javascript-todaySynchronous and asynchronous callbacks#
The callbacks above run immediately, during the call (synchronous). Other callbacks run later, when something happens:
console.log("1. Start");
setTimeout(() => console.log("3. Timer finished"), 0);
console.log("2. End of script");1. Start
2. End of script
3. Timer finishedEven with a 0 millisecond delay, the timer callback waits until the current code has finished. Event handlers, timers and network requests all use asynchronous callbacks. Asynchronous Code and the Event Loop explains why.
Callback hell#
When asynchronous steps depend on each other, nested callbacks pile up:
getUser(id, (user) => {
getOrders(user, (orders) => {
getInvoice(orders[0], (invoice) => {
sendEmail(invoice, () => {
console.log("Done");
});
});
});
});This "pyramid" is hard to read and to handle errors in. Promises and async/await solve it (Promises), so modern code writes the same steps one after another.
Practice#
Practice exercise
Write countWhere(array, test) that returns how many items pass the test, and use it to count words longer than 3 letters and numbers divisible by 5.
Show solution
function countWhere(array, test) {
let count = 0;
for (const item of array) {
if (test(item)) count++;
}
return count;
}
console.log(countWhere(["a", "tree", "is", "green"], (w) => w.length > 3));
console.log(countWhere([5, 12, 20, 33, 45], (n) => n % 5 === 0));2
3The built-in equivalent is array.filter(test).length.
Key takeaway#
Finished this lesson?