Skip to content

Latest commit

 

History

History
496 lines (355 loc) · 15 KB

README.md

File metadata and controls

496 lines (355 loc) · 15 KB

Callbacks & Iterator Methods

Why is this important?

This workshop is important because:

Using callback functions is an effective way to write declarative, functional JavaScript. JavaScript was built to deal with asynchronous events; callbacks help appropriately time and coordinate our program.

What are the objectives?

After this workshop, developers will be able to:

  • Pass a function as a callback to another function
  • Use iterator methods with callbacks to build more declarative loop structures
  • Recognize the best iterator method for a particular use case
  • Build iterator methods from scratch

Where should we be now?

Before this workshop, developers should already be able to:

  • Write and call functions in JavaScript
  • Explain what a higher order function is
  • Use a for loop

Callbacks: Review

callback

A callback is a function that is passed into another function as an argument and then used. A function that can take in a callback as an argument is known as a higher order function.

Let's review with an example.

function masterMathFunction(a, b, callback) {
    console.log("the inputs are: " + a + " and " + b);
    callback(a,b);
}

function multiplyMe(n,m) {
    console.log("Product: " + n*m);
}

function raiseMe(n,m) {
    console.log("Power: "+ Math.pow(n,m));
}

masterMathFunction(2,3,raiseMe);
masterMathFunction(2,3,multiplyMe);
  1. What is the higher order function here?

  2. What function(s) may be used as callbacks for the higher order function?

  3. What is another possible callback function?

Callbacks allow us to queue up the execution of a function until after some other code completes. They allow for asynchronous behavior, even though JavaScript is a single-threaded language. They also let us customize behaviors inside libraries.

See this awesome video of a talk by Philip Roberts on how JavaScript works.

Check for Understanding

Let's walk through another example of code that uses callbacks:

var element = document.querySelector("body");
var counter = 0;
element.addEventListener("click", countClicks);

function countClicks(event){
  counter += 1;
  console.log("clicked " + counter + " times.");
}

Let's run this in the console.

####Anonymous Functions: Review

Often, if a callback will only be used with one higher order function, the callback function definition is written inside the higher order function call.

var element = document.querySelector("body");
var counter = 0;
element.addEventListener("click", function(event){
  counter += 1;
  console.log("clicked " + counter + " times.");
});

In these cases, the callback often won't be given a name. A function without a name is called an anonymous function.

Independent Practice: sort

JavaScript's built-in sort method for arrays sorts numbers from lowest to highest value and String items as strings, alphabetically.

var arr = [1, 2, 125, 500];
arr.sort();
//=> [1, 125, 2, 500]

Checking the documentation, you should notice there is an optional compareFunction parameter that can change the sort order rules.

Work in a snippet or in your console. Use JavaScript's sort function to sort the following objects by price, from lowest to highest:

var items = [
  { name: "trail mix", price: 3.50 },
  { name: "first aid kit", price: 20.00 },
  { name: "water bottle", price: 12.00 },
  { name: "flashlight", price: 8.00 },
  { name: "gps unit", price: 93.00 }
];
Hint: how to start You'll need to write a custom `compareFunction` and pass it into the `sort` method. Follow the structure of the custom `compareFunction` from the documentation.
Answer: the compare function ```js function compareByPrice(item1, item2){ if (item1.price < item2.price) { return -1; } if (item1.price > item2.price) { return 1; } // items must have equal price return 0; } ```
Answer: calling `sort` with customized function ```js items.sort(compareByPrice); ```

Iterator Methods

Iteration basically means looping.

looping

var potatoes = ["Yukon Gold", "Russet", "Yellow Finn", "Kestrel"];
for(var i=0; i < potatoes.length; i++){
    console.log(potatoes[i] + "!")
}

Iterator methods create more declarative abstractions for common uses of loops.

var potatoes = ["Yukon Gold", "Russet", "Yellow Finn", "Kestrel"];
potatoes.forEach(function(element){
  console.log(element + "!")
});

We can combine our knowledge of callbacks & iteration to write better, more declarative code.

We can also illustrate a lot of these with the physical example of a pillbox(array) with items at each index(jelly beans!).

Be thinking about how we could extend this metaphor with each method we discuss here.

The forEach() method performs whatever callback function you pass into it on each element.

var fruits = ["Apple", "Banana", "Cherry", "Durian", "Elderberry",
"Fig", "Guava", "Huckleberry", "Ice plant", "Jackfruit"];

fruits.forEach(function (value, index) {
    console.log(index + ". " + value);
});

// 0. Apple
// 1. Banana
// 2. Cherry
// 3. Durian
// 4. Elderberry
// 5. Fig
// 6. Guava
// 7. Huckleberry
// 8. Ice plant
// 9. Jackfruit
//=> ["Apple", "Banana", "Cherry", "Durian", "Elderberry",
//    "Fig", "Guava", "Huckleberry", "Ice plant", "Jackfruit"];

Similar to forEach(), map() traverses an array. This method, however performs the callback function you pass into it on each element and then outputs the results inside a new array.

Often we want to do more than just perform an action, like console.log(), on every loop. When we actually want to modify/manipulate our array, map is the go-to!

####Example: Double every number

var numbers = [1, 4, 9];
var doubles = numbers.map(function doubler(num) {
  return num * 2;
});
// doubles is now [2, 8, 18]. numbers is still [1, 4, 9]

####Example: Pluralize all the fruit names

pluralized_fruits = fruits.map(function pluralize(element) {

  // if word ends in 'y', remove 'y' and add 'ies' to the end
    var lastLetter = element[element.length -1];
     if (lastLetter === 'y') {
      element = element.slice(0,element.length-1) + 'ie';
  }

    return element + 's';
});

fruits // ORIGINAL ARRAY IS UNCHANGED!
//=> ["Apple", "Banana", "Cherry", "Durian", "Elderberry",
//    "Fig", "Guava", "Huckleberry", "Ice plant", "Jackfruit"];

pluralized_fruits // MAP OUTPUT
//=> [ "Apples", "Bananas", "Cherries", "Durians", "Elderberries",
//    "Figs", "Guavas", "Huckleberries", "Ice plants", "Jackfruits"  ]

####Example: Square each number

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

numbers.map(function square(element) {
  return Math.pow(element, 2);
});
//=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Check for Understanding: Etsy Merchant

Elaine the Etsy Merchant thinks her prices are scaring off customers. Subtracting one penny from every price might help! Use `map` to subtract 1 cent from each of the prices on this receipt's list:
var prices = [3.00, 4.00, 10.00, 2.25, 3.01];
// create a new array with the reduced prices...
var prices = [3.00,4.00,10.00,2.25,3.01];
var reducedPrices = prices.map(function(price) {
  return price - 0.01;
});

With the filter(), method you can create a new array filled with elements that pass certain criteria that you designate. This is great for creating a new filtered list of movies that have a certain genre, fruits that start with vowels, even numbers, and so on.
It's important to remember that a filter method on an array requires a boolean return value for the callback function.

Example: Return a list of fruits that start with vowels

var fruits = ["Apple", "Banana", "Cherry", "Elderberry",
"Fig", "Guava", "Ice plant", "Jackfruit"];
var vowels = ["A", "E", "I", "O", "U"];
function vowelFruit(fruit) {
  var result = vowels.indexOf(fruit[0]) >= 0; // indexOf returns -1 if not found
  // console.log("result for " + fruit + " is " + result);
  return result;
}
var vowelFruits = fruits.filter(vowelFruit);
console.log(vowelFruits);
// ["Apple", "Elderberry", "Ice plant"]

Or alternatively:

var vowels = ["A", "E", "I", "O", "U"];

var vowelFruits = fruits.filter(function vowelFruit(fruit) {
  return vowels.indexOf(fruit[0]) >= 0; // indexOf returns -1 if not found
});
console.log(vowelFruits);
// ["Apple", "Elderberry", "Ice plant"]

Example: Find all the even numbers that are greater than 5

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

even = numbers.filter(function filterEvens(num) {
  var isEven = num%2==0;
  var greaterThanFive = num > 5;
  return isEven && greaterThanFive;
});
//=> [6, 8, 10]

Check for Understanding: Birthdays

Is there an interesting trend in birthdays? Do people tend to be born more on even-numbered dates or odd-numbered dates? If so, what's the ratio? In class, let's take a quick poll of the days of the month people were born on. This is a great chance to do some serious science!
var exampleBdays = [1, 1, 2, 3, 3, 3, 5, 5, 6, 6, 8, 8, 10, 10, 12, 12, 13, 13, 15, 17, 17, 18, 20, 20, 26, 31];
// gather an array of all the even birthdays...
var exampleBdays = [1, 1, 2, 3, 3, 3, 5, 5, 6, 6, 8, 8, 10, 10, 12, 12, 13, 13, 15, 17, 17, 18, 20, 20, 26, 31];
var birthDateEvens = exampleBdays.filter(function(birthday) {
  return birthday % 2 === 0 ? birthday : false;
});

The reduce() method is designed to create one single value that is the result of an action performed on all elements in an array. It essentially 'reduces' the values of an array into one single value.

Example: Find the sum of all of the numbers in an array

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var sum = numbers.reduce(function add(previous, current) {
  return current + previous;
});

//=> 55

In the above examples, notice how the first time the callback is called it receives element[0] and element[1].

There is another way to call this function and specify an initial starting value.

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var sum = numbers.reduce(function add(previous, current) {
  return current + previous;
}, 100);

//=> 155

In the above example, the first time the callback is called it receives 100 and 1.

Note: We set the starting value to 100 by passing in an optional extra argument to reduce.

Check for Understanding: Test Scores

Roberto has been tracking his test scores over the semester. Use `reduce` to help you find his average test score.
var scores = [85, 78, 92, 90, 98];
var scores = [85, 78, 92, 90, 98];
var total = scores.reduce(function(previous, current) {
  return previous + current;
});
var average = total/(scores.length);

Discussion: forEach

So how does forEach work?

Let's think about forEach again. What's happening behind the scenes?

  • What are our inputs?
  • What is our output?
  • What happens on each loop?
  • What does the callback function do?
  • What gets passed into our callback function? That is, what are its inputs/parameters?
  • Where does the callback come from?

Let's check:

function print(item) {
  console.log(item);
}

[0, 100, 200, 300].forEach(function(number) {
  print(number);
});
Given the above, how would you build a function that mimics `forEach` yourself? Call it `myForEach`.
function myForEach(collection, callback) {
  for(var i = 0; i < collection.length; i++) {
    callback(collection[i]);
  }
}
// the below should have the same result as the above
myForEach([0, 100, 200, 300], print)

Closing Thoughts

  • Callbacks are a very common pattern in JavaScript.
  • Iterator methods help us write more conventional, declarative code. Loops (for and while) are more imperative and offer greater flexibility (configuration).