More Callbacks and Iterators
Programming with functions! Weren't we already doing that? Well yes, but we can use functions more heavily, especially in place of loops.
Objectives
- Understand how functions can be passed in and out of other functions
- Run syntax to replace loops with the four main JavaScript iterators: forEach, map, reduce, filter
- Learn to sort arrays with
sort
Iterators
Iterators are examples of functional programming replacements for for
loops. We can use these functions to perform common Array operations for us.
What might we want to with an array?
- run some piece of logic on each item
- create a new array with each item being slightly transformed
- filter the array to contain only a subset of items
- combine all the items in some fashion
- sort
We could accomplish all of this using for
loops, but writing for
loops is error prone and tiring. JavaScript provides iterator functions for all of these common operations. They are called (respectively):
- forEach
- map
- filter
- reduce
Declare an array
- Call an iterator on the array
- Pass a function to the iterator
- Get results
forEach
forEach
is the functional programming replacement for your standard for
loop. You can take the body from your for
loop, wrap it in a function, and pass that argument to forEach
. Let's look at an example:
var friends = ["Markus", "Tim", "Ilias", "Elie"];
// old way, with a for loop
for (var i = 0; i < friends.length; i++) {
console.log("Hello, " + friends[i] + "!");
}
// cool new way, with the .forEach iterator
friends.forEach( buddy =>{
console.log("Hello, " + buddy + "!");
});
// both output the same thing
// > Hello, Markus!
// > Hello, Tim!
// > Hello, Ilias!
// > Hello, Elie!
Try it
Use the .forEach
iterator to loop over the following
array of foods and say you like them.
var foods = ["pizza", "tacos", "ice cream"];
// your code here
// The output should be
// > "I like pizza"
// > "I like tacos"
// > "I like ice cream"
Try it again
Use the .forEach
iterator to loop over the following
array of objects and say how delicious each one is.
var foods = [
{name: "Pizza", level: "very"},
{name: "Tacos", level: "mostly"},
{name: "Cottage Cheese", level: "not very"}
];
// your code here
// The output should be
// > Pizza is very delicious
// > Tacos is mostly delicious
// > Cottage Cheese is not very delicious
map
Sometimes we want to loop over an array and build a new array in the
process. This is what map
helps us solve. It is like forEach
, but
it returns the new array that is created.
var names = ["tim", "ilias", "elie", "markus"];
// old way with for loop
var cased = [];
for (var i = 0; i < names.length; i++) {
cased.push(names[i].toUpperCase());
}
console.log(cased);
// new way with `map`
var cased = names.map( person => {
return person.toUpperCase();
});
console.log(cased);
// Should output
// > ["TIM", "ILIAS", "ELIE", "MARKUS"]
// > ["TIM", "ILIAS", "ELIE", "MARKUS"]
filter
Filter is an iterator that loops through your array and filters it down to a subset of the original array. A callback is called on each element of the original array: if it returns true, then the element is included in the new array, otherwise it is excluded.
var names = ["tim", "ilias", "elie", "markus"];
var isEven = name => {
return name.length % 2 === 0;
};
var isOdd = name => {
return name.length % 2 !== 0;
};
var evenLengthNames = names.filter(isEven);
var oddLengthNames = names.filter(isOdd);
console.log(evenLengthNames);
console.log(oddLengthNames);
// Should output
// > ["elie", "markus"]
// > ["tim", "ilias"]
reduce
Reduce iterates over an array and turns it into one, accumulated
value. In some other languages it is called fold
.
var nums = [1,2,3,4];
var add = (a, b) => {
return a + b;
};
var sum = nums.reduce(add);
console.log(sum);
// Should output:
// > 10
// which is, 1 + 2 + 3 + 4
Reduce also usually accepts an option third parameter that will be the initial accumulated value. If it is omitted, then the reduction starts with the first two values in the array.
var nums = [1,2,3,4];
var add = (a, b) => {
return a + b;
};
var sum = nums.reduce(add, 10);
console.log(sum);
// Should output:
// > 20
// which is, 10 + 1 + 2 + 3 + 4
sort
items.sort((a, b) => {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
// equal
return 0;
});
Pairing Exercises
- create an
index.html
andscript.js
- repeat all the code above
Further
- copy / paste the pokedex into your file
- use
reduce
to help you create an average pokemon weight - use
map
to get an array of all the first letters of the pokemon names. - use filter to find
Fearow
Further
Investigate some other array methods: find
, flat
, every
here.
Resources
Here are some good blog posts that break down map
, filter
, and reduce
.
- Transforming Arrays with Array#map
- Transforming Arrays with Array#filter
- Transforming Arrays with Array#reduce
- Sorting Arrays
Extra
Write your own callback
We can write a function that does something we can specify outside the function.
var alertFiveTimes = (stringCallback) => {
for( var i=0; i<5; i++){
alert( stringCallback() );
}
};
var encouraging = () => {
return "awesome!!";
};
var jerk = () => {
return "ewww, gross";
};
alertFiveTimes( encouraging );
alertFiveTimes( jerk );
A more complete example is one that can do a generic operation - in this case on a set of 2 numbers - and then return the result.
The individual operation we can define somewhere else.
var add = (a,b) =>{
return a + b;
};
var listOfNumbers = [91,52,53,54,5,6,7,8];
var listOfNumbers = [1,1,1,1];
var repeatOperation = (numbers, callback)=>{
var currentResult = 0;
for( var i=0; i<numbers.length; i++){
console.log(currentResult);
currentResult = callback(currentResult, numbers[i]);
}
return currentResult;
};
var total = repeatOperation(listOfNumbers, add);
This makes the function we wrote flexible, because it can accomplish more than one kind of task
var subtract = (a,b)=>{
return a - b;
};
var listOfNumbers = [100,10,10,10];
var total = repeatOperation(listOfNumbers, add);
Returning functions from functions
You can probably guess by now how to return a function from a function:
var drinkMaker = (drinkType)=> {
return function(drinkVolume) {
return {
drink: drinkType,
volume: drinkVolume
};
};
};
var cocktailMaker = drinkMaker('cocktail');
var smoothieMaker = drinkMaker('smoothie');
cocktailMaker('4oz');
smoothieMaker('16oz');
We'll see that if we print the contents of cocktailMaker
and smoothieMaker
, we get functions that were returned from the function drinkMaker
. Now, we can call these functions and pass in additional arguments. Neat!
Functional programming is a popular topic in the context of js, so we'll be seeing more examples like these in the future. Like with iterators!
Further Pairing Exercise
copy paste and run the write your own callback code
- add your own string-returning function and use the alertFiveTimes function to call it.
Write a function that uses a loop and does multiplication on two numbers-
Given:
var add = (a,b)=>{
return a + b;
};
Calling the function will look like this:
multiply( add, 2 , 3 );
The result should be 6.
write a console stopwatch function. When is starts, console log the current stopwatch time in seconds in this format:
hours:minutes:seconds
write code that will set a countdown timer. Prompt the user for how many seconds they want the timer to last, and when you get their answer, start the timer.
write your own foreach function that takes a callback parameter that will run for each iteration.