Functions as arguments

As functions are treated like data in JavaScript, we can pass functions as arguments to other functions. A function passed as an argument is referred to as a callback function. We expect the callback function to be invoked by the function that takes the callback.

Here is a common reason for having a function that can take another function as argument: abstraction. Modularity encourages code reuse. To modularize our code base, we need to abstract away those specific operations or functionalities that should be in separate utility/library functions. If a function func() requires a specific operation implemented by a utility function, we pass the ulitity function to func(). A function that can take another function as argument encourages us to think in terms of code reuse, to have utility functions each of which does a specific task and does it well.

Pass a function name

How exactly do we pass a callback to another function? We pass the name of the callback to whichever function func() that accepts a callback. The function func() would then have a reference it can use to invoke the callback. Consider the following example.

const joy = () => console.log("Yay! \\o/"); const emotion = (callback) => { console.log("My current mood"); callback(); // invoke the callback }; emotion(joy); // pass name of callback

We pass the name of the function joy() to the function emotion(). Note that we pass the name joy to emotion() to result in the expression emotion(joy).

Why not have the expression emotion(joy())? Consider the following example.

const joy = () => console.log("Yay! \\o/"); const emotion = (callback) => { console.log("My current mood"); callback(); // invoke the callback }; emotion(joy()); // pass result of function call

We are not passing the name of a function, but rather execute the function joy() and pass the result of the invocation to emotion(). The result of the invocation joy() is undefined because the method console.log() does not have a return value. In the body of emotion() where it attempts to invoke the callback function, i.e. the expression

callback();

we would receive an error because undefined is not a function. When passing a function as an argument, we pass the name of the function, not invoke the function.

Many methods in the standard JavaScript library accept a callback function. For example, the array method forEach() takes a callback function. The method iterates over each element of a given array and invokes the callback on the current element. Here is an example of the method forEach() in action.

const fruit = ["apricot", "banana", "carambola"]; const print = (name) => console.log(name); fruit.forEach(print);

Anonymous functions can be used as callback functions. The method setTimeout() is another method from the standard JavaScript library that accepts a callback function. Consider the following example.

const alarm = (n) => { if (n < 1) { console.log("Tick tock, Mr. Wick."); return; } console.log(n); const delay = 1000; setTimeout(() => alarm(n - 1), delay); }; alarm(5);

As a final example, sometimes we need to call a function multiple times. Instead of using an explicit loop to invoke the function within the loop body, we opt for the functional approach and declare a function repeat(). The function has 2 parameters: the number of times we want to invoke a function, and the name of the callback function. The following implementation uses anonymous function.

const repeat = (n, fn) => { [...Array(n).keys()].forEach(() => fn()); }; const hi = () => console.log("Hello"); repeat(3, hi);

Pass arguments to callback functions

The array method forEach() takes a callback function and implicitly passes each array element to the callback, one at a time. How would we explicitly pass an argument to a callback function? The function accepting a callback must also take an argument to be passed to the callback. By way of illustration, the next example declares a function speak() that takes a callback and a parameter to be passed to the callback.

const speak = (callback, name) => callback(name); const cat = (name) => console.log(`${name} says meow`); const dog = (name) => console.log(`${name} says woof`); speak(cat, "Tabby"); speak(dog, "Fido");

Here's a more complicated example. We want a function keep() that has 2 parameters: a callback function and an array. The function keep() invokes the callback on each element of the given array. The callback returns true if the argument given to it passes a test; returns false otherwise. The function keep() returns an array of all elements of the given array that pass the test implemented by the callback. For concreteness, consider the following script.

const keep = (callback, arr) => { const newArr = []; for (const a of arr) { if (callback(a)) { newArr.push(a); } } return newArr; }; const isPet = (name) => { switch (name) { case "bird": case "cat": case "dog": case "hamster": return true; default: return false; } }; const pet = keep(isPet, ["bard", "cat", "dog", "ham"]); console.log(pet);

Our callback is the function isPet(), which returns true if a string represents a pet animal and false otherwise. We pass the function name isPet to the function keep(), together with an array of strings. The function keep() invokes isPet() on each element of the array and returns an array of strings that represent pet animals. As a side note, the function keep() mimics the array method filter().

The next example illustrates the power of functions whose parameters consist of other functions. The static method Math.max() takes 1 or more numbers and returns the maximum of the given numbers. We want to implement a general version of Math.max(). Before we get to the general, we start with the particular. Consider the following implementation.

const max = (arr) => { let high = arr[0]; arr.forEach((e) => { high = Math.max(high, e); }); return high; }; const array = [27, 35, 49, 23, 4, 1, 46, 6, 12, 1]; console.log(max(array));

In order to generalize our function max(), we must abstract away this particular expression:

high = Math.max(high, e);

The method Math.max() assumes numeric data, hence would fail to compare other data types such as objects or strings. We want to pass to max() a callback function whose job is to serve as a maximum function. If the array given to max() consists of strings, the callback should be able to deal with strings and return the "maximum" of 2 strings. If the array consists of objects, the callback should return the "maximum" of 2 objects. In the following script, we abstract away the above line and invoke the callback on 2 array elements.

const max = (fn, arr) => { let high = arr[0]; arr.forEach((e) => { high = fn(high, e); }); return high; }; const language = [ "Ada", "Alice", "Claire", "JADE", "JEAN", "LISA", "Julia", "Mary", "Miranda", ]; const number = [2, 45, 7, 1, 7, 13, 0, 5]; const pet = [ { name: "Tabby", age: 2 }, { name: "Fido", age: 1 }, { name: "Iegor", age: 3 }, { name: "Hamsuke", age: 2 }, ]; const oldest = (a, b) => (a.age < b.age ? b : a); const longest = (a, b) => (a.length < b.length ? b : a); console.log(max(Math.max, number)); console.log(max(oldest, pet)); console.log(max(longest, language));

Exercises

Exercise 1. Modify the script fruit.js so that an anonymous function is passed to the method forEach().

Exercise 2. Modify the script repeat.js to implement the function repeat() via a recursive strategy.

Exercise 3. Without using the array method forEach(), implement your own version of forEach(). Your implementation should accept 2 parameters: a callback function and an array.

Exercise 4. Implement a function sleep() that takes 2 parameters: a callback function and the number of milliseconds to sleep. Model the parameters upon the method setTimeout(), but do not use the method in your implementation. Modify the script alarm.js to use your function sleep().

Exercise 5. A function that accepts a callback can use the operator typeof to check that the passed in function is indeed a function. Modify the script keep.js to ensure that, in the function keep(), the parameter callback is indeed a function.

Exercise 6. Refer to the function keep() from the script keep.js. Implement a callback function that returns true if a number is even and returns false otherwise. Test your callback function against an array of all integers between 0 and 10, inclusive.

Exercise 7. Declare a function every() having 2 parameters: a callback function and an array. The function every() returns true if each element in the array passes the test implemented by the callback function. If one of the array elements does not pass the test implemented by the callback, then every() returns false. As an example, declare your callback to be a function that returns true if a character is part of the English alphabet and false otherwise. Do not use the array method every().

Exercise 8. This is similar to the previous exercise, but with a twist. Declare a function some() that has 2 parameters: a callback and an array. The function some() returns true if one of the array elements satisfies the test implemented by the callback. In case the callback returns false for each array element, then some() returns false. For concreteness, declare your callback to be a function that returns true if an integer is even and false otherwise. Do not use the array method some().

Exercise 9. Consider an array arr of all integers between 0 and 100, inclusive. Use your function some() to help you determine whether arr has a particular element. For example, use some() to test whether arr has the integer 42. Now test to see whether arr has the number 101.

Exercise 10. The array method sort() accepts a function compareFn() that implements a sorting order. The method passes 2 array elements a and b to the compare function compareFn(), which should return one of the following results:

  1. A positive integer if a comes after b, i.e. the ordering [b, a].
  2. A negative integer if a precedes b, i.e. the ordering [a, b].
  3. Zero if the original ordering is to be kept.

To sort an array of integers in non-decreasing order, our compare function can be:

const ascend = (a, b) => a - b;

Use the above compare function to sort the following array:

const arr = [2, 45, 7, 1, 7, 13, 0, 5];

Declare a compare function that sorts an array in non-increasing order.

Exercise 11. Implement a function map() having 2 parameters: a callback function and an array. The function map() applies the callback on each element of the array and returns an array of results from invoking the callback. Do not use the array method map().

  1. Implement map() using either a for or while loop.
  2. Rewrite your implementation to not use either of for or while.
  3. Declare your callback to be a function that increments a number by 1. Use this function to test your map().
  4. Declare a callback that squares a number. Use this function to test your map().

results matching ""

    No results matching ""