Chapter 3 · Data
Callbacks: functions on arrays
Step 1 of 22 · Concept
Give a function, let another call it
By the end of this lesson you hand functions to arrays to make new arrays, choose, total and sort.
const scores = [72, 45, 90];
scores.forEach((s) => console.log(s));
A callback is a function you give to another function to call. Here forEach calls the arrow function once per
item, in order.
Step 2 of 22 · Demo
forEach and for...of side by side
Both halves print the three scores, in order. Run it.
Then change the arrow function's body to console.log(s + 1) and run it again: only the first three lines change.
Step 3 of 22 · Concept
map makes a new array
const xs = [1, 2, 3];
const doubled = xs.map((x) => x * 2);
map calls the arrow function once per item and collects what each call returns into a new array. It does the work of
lesson 10's loop with push:
const doubled = [];
for (const x of xs) {
doubled.push(x * 2);
}
Step 4 of 22 · Quiz
Did the old array change?
Type the missing line.
const prices = [2, 3.5];
const doubled = prices.map((p) => p * 2);
console.log(doubled.join(" "));
console.log(prices.join(" "));
Step 5 of 22 · Demo
See it run
Run it and compare with your answer. Then change p * 2 to p + 1 and run it again.
Step 6 of 22 · Concept
filter keeps, find picks one
Each of these calls its callback per item. The callback answers true or false. filter does the work of lesson
10's loop that kept the scores over 50, an if and a push.
| method | gives, with (s) => s >= 50 on [72, 45, 90] |
|---|---|
filter | a new array of the passing items: [ 72, 90 ] |
find | the first such item, 72, or undefined when none |
some | true: at least one item passes |
every | false: not all items pass |
Step 7 of 22 · Demo
Ask the scores
The first callback reads limit, a name around it, as any function can (lesson 7). Run it.
Then change limit to 80 and run it again.
Step 8 of 22 · Exercise
Your turn: who passed?
Log the names of the students with a score of 50 or more:
filterthe students with 50 or more.mapeach of those to its name.- Log them, joined with
", ".
The program must print:
Passed: Ada, Cy
filter keeps whole records, not names: joined, they print as [object Object]. A map after it turns each record into its name.
filter with a callback that compares s.score with 50; then map with a callback that returns s.name; then join(", ") in a template literal.
const names = passed.map((s) => s.name);
Step 9 of 22 · Concept
reduce folds an array into one value
const scores = [90, 75, 60];
const sum = scores.reduce((total, x) => total + x, 0);
reduce calls the callback once per item, with two values:
| parameter | holds |
|---|---|
total | the running total: at first the 0 after the callback |
x | the next item |
What each call returns becomes the next call's total. The last call's answer is what reduce gives: here 225.
Step 10 of 22 · Trace
Watch the total grow
reduce calls the arrow function, so its frame sits on top of reduce's. Press Next to see one call at a time.
const scores = [90, 75, 60];
const sum = scores.reduce((total, x) => total + x, 0);
console.log(sum);reducecalls the arrow function for the first item:totalis0, the start;xis90.- The first call returned
90. The second call gets it astotal, withx75. - The second returned
165. The third call getstotal165andx60. - The third returned
225, and so didreduce:sumis225. Line 3 is next. - Line 3 logged
225. The program is finished.
Step 11 of 22 · Concept
Choose a value: c ? a : b
The conditional operator c ? a : b gives a when c is truthy, else b. It chooses a value, where if
chooses which lines run:
const s = 72;
console.log(s >= 50 ? "pass" : "fail");
This logs pass. It fits in an arrow function's body:
const label = (s) => (s >= 50 ? "pass" : "fail");
Step 12 of 22 · Demo
A report in three lines
filter gives a new array, so .map can follow it at once: a chain. Run it.
Then change s >= 40 to s >= 50 and run it again.
Step 13 of 22 · Exercise
Your turn: the cart
- Add up the prices with
reduce. - Log the total with two decimals,
toFixed(2). - Delivery is free at 10 or more: log
yesornowith the conditional operator.
The program must print:
Total: 7.35
Free delivery: no
Start reduce at 0. Without it, the first record is the first total, the sum becomes text, and Node stops with TypeError: total.toFixed is not a function.
The callback gets the running sum and the next item, and returns the sum plus item.price. Then compare the total with 10 inside ? :.
const free = total >= 10 ? "yes" : "no";
Step 14 of 22 · Concept
sort compares as text
const xs = [10, 9, 1, 100, 25];
xs.sort();
console.log(xs);
This logs [ 1, 10, 100, 25, 9 ]. With no callback, sort turns each item into text and compares the texts, as
< does (lesson 9). "100" comes before "25" because "1" comes before "2".
sortchanges the array itself.- It also returns that same array.
Step 15 of 22 · Quiz
Predict the sorted numbers
What does this program log?
const xs = [10, 9, 1];
xs.sort();
console.log(xs.join(" "));
Step 16 of 22 · Demo
See it run
Run it and compare with your answer. Then put 100 into the array and run it again.
Step 17 of 22 · Concept
Tell sort how to compare
Give sort a comparator: a callback that compares two items, a and b. Only the sign of its answer counts.
| write | does |
|---|---|
sort((a, b) => a - b) | numbers, lowest first |
sort((a, b) => b - a) | highest first |
sort((a, b) => a.name.localeCompare(b.name)) | by name: below 0 when a.name comes first |
toSorted(...) | a new sorted array; the old one stays |
Math.max(...xs) | the largest number |
Step 18 of 22 · Demo
Sorting done right
Run it. ...xs hands the items to Math.max one by one.
Step 19 of 22 · Exercise
Your turn: a leaderboard
- Sort the players by score, highest first.
- Log each one as its name and score, with
forEach.
Cy and Ada share 90. Equal items keep their order, so Cy stays first. The program must print:
Cy 90
Ada 90
Bob 85
Di 70
Mind the order of a and b: a.score - b.score puts the lowest score first, Di 70.
players.sort with a comparator on score, then players.forEach with an arrow function that logs a template literal.
players.sort((a, b) => b.score - a.score);
Step 20 of 22 · Check yourself
Check yourself: the class report
- Log the average score.
- Log the names with 50 or more, sorted by name.
- Log the best score, with
Math.max.
The output must be:
Average: 78.75
Passed: Ada, Cy, Di
Best: 95
Make an array of the scores alone first, with map: both the average and Math.max need it. The names are a chain of three.
Step 21 of 22 · Challenge · optional
Challenge: ties by name
Optional.
The leaderboard again, but players with equal scores now go by name. The program must print:
Ada 90
Cy 90
Bob 85
Di 70
For Cy and Ada, b.score - a.score is 0: a tie. Only then should the names decide.
x || y gives x when x is truthy, else y (lesson 11). 0 is falsy, so on a tie || gives its right side.
(a, b) => b.score - a.score || a.name.localeCompare(b.name)
Step 22 of 22 · Recap
Recap
- A callback is a function you hand to another function to call.
mapmakes a new array;filterkeeps items;findgives the first match orundefined.reduce((total, x) => total + x, 0)folds an array into one value.c ? a : bchooses a value.sort()alone compares as text; give it(a, b) => a - b. Ties keep their order.
Next: lesson 13 looks things up by key with a Map, and keeps each value once with a Set.
Review
2 questions from this lesson, in a new order each time. Answer from memory: 2 of 2 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
const scores = [72, 45, 90];
scores.forEach((s) => console.log(s));
for (const s of scores) {
console.log(s);
}
const prices = [2, 3.5];
const doubled = prices.map((p) => p * 2);
console.log(doubled.join(" "));
console.log(prices.join(" "));
const scores = [72, 45, 90];
const limit = 50;
const passed = scores.filter((s) => s >= limit);
console.log(passed.join(", "));
console.log(scores.find((s) => s < 50));
console.log(scores.find((s) => s > 100));
console.log(scores.some((s) => s > 80));
console.log(scores.every((s) => s > 80));
const students = [
{ name: "Ada", score: 72 },
{ name: "Bob", score: 45 },
{ name: "Cy", score: 90 },
];
// 1. filter: the students with 50 or more
// 2. map: each of those to its name
// 3. log Passed: and the names, joined
const scores = [72, 45, 90, 38];
const report = scores
.filter((s) => s >= 40)
.map((s) => (s >= 50 ? `${s} pass` : `${s} fail`));
console.log(report.join(", "));
const total = scores.reduce((sum, s) => sum + s, 0);
console.log("Total:", total);
const cart = [
{ name: "tea", price: 2.5 },
{ name: "cake", price: 3.1 },
{ name: "jam", price: 1.75 },
];
// 1. add up the prices with reduce
// 2. log the total with two decimals
// 3. log yes or no for free delivery
const xs = [10, 9, 1];
xs.sort();
console.log(xs.join(" "));
const xs = [10, 9, 1, 25];
console.log(xs.toSorted((a, b) => a - b).join(" "));
console.log(xs.join(" "));
xs.sort((a, b) => b - a);
console.log(xs.join(" "));
console.log("Max:", Math.max(...xs));
const people = [
{ name: "Cy", score: 90 },
{ name: "Ada", score: 70 },
{ name: "Bob", score: 80 },
];
people.sort((a, b) => b.score - a.score);
console.log(people.map((p) => p.name).join(" "));
people.sort((a, b) => a.name.localeCompare(b.name));
console.log(people.map((p) => p.name).join(" "));
const bad = [3, 1, 2, 5, 4];
bad.sort((a, b) => a > b);
console.log(bad.join(" "));
const players = [
{ name: "Bob", score: 85 },
{ name: "Cy", score: 90 },
{ name: "Di", score: 70 },
{ name: "Ada", score: 90 },
];
// 1. sort the players, highest score first
// 2. log each player's name and score
const students = [
{ name: "Di", score: 95 },
{ name: "Bob", score: 45 },
{ name: "Ada", score: 80 },
{ name: "Cy", score: 95 },
];
// 1. the average score
// 2. the names with 50 or more, by name
// 3. the best score
const players = [
{ name: "Bob", score: 85 },
{ name: "Cy", score: 90 },
{ name: "Di", score: 70 },
{ name: "Ada", score: 90 },
];
players.sort((a, b) => b.score - a.score);
players.forEach((p) => console.log(`${p.name} ${p.score}`));