Higher-order Functions You Already Use
Tags: javascript, webdev, programming, beginners
Published: 2024-04-09 19:26:21

You may not hear much about [higher-order functions](https://en.wikipedia.org/wiki/Higher-order_function) (HoFs), but there are plenty of examples in everyday JavaScript. Let's take a quick look at how they are used and some common patterns.

## Callbacks & HoFs

Higher-order functions _accept_ or _return_ a function. While they can take many shapes, most of the time we are dealing with functions that accept _callbacks_. If you've ever passed a callback to a function, you've used this functional programming concept!

## Events

Perhaps the most common HoFs are for events. It doesn't matter whether you use a library, a framework, or write in plain JavaScript; event handlers are almost always callbacks passed to a higher-order function like [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener).

```javascript
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', (event) => {
  console.log('This is a callback function!');
  console.log('We passed this to addEventListener.');
  console.log('You clicked', event.target);
});

// Or in JSX
const Button = () => (<button onClick={
  () => console.log('I\'m a callback!')
}>Test</button>);

```

### What's in a Parameter Name?

An `Event` is passed as the first argument, no matter what you name it.

```javascript
// Some people prefer "e" or "evt"
myButton.addEventListener('click', (e) => {
  console.log('You clicked', e.target);
});

// The name is your choice. The value is always an Event
myButton.addEventListener('click', (orange) => {
  console.log('You clicked', orange.target);
});
```

It's still there even if you don't give it a name!

```javascript
myButton.addEventListener('click', function () {
  // "arguments" is not available for arrow functions
  // but the first argument is still an Event
  // even if you don't use it.
  console.log('You clicked', arguments[0].target);
});
```

But the callback function doesn't have to be anonymous. Once you know the arguments a callback receives – like an `Event` as the first argument _no matter what_ – you can feel more confident splitting up the code.

```javascript
const myButton = document.getElementById('myButton');
const theButtonAction = (event) => {
  console.log('This is still a callback function!');
  console.log('We passed this to addEventListener.');
  console.log('You clicked', event.target);
};

// This will pass an Event to myButtonAction
myButton.addEventListener('click', theButtonAction);
```

This shows us one of the more powerful parts of HoFs: callbacks can be reused rather than creating new ones each time.

```javascript
const myButton = document.getElementById('myButton');
const yourButton = document.getElementById('yourButton');

// Re-use the event handler on two different buttons
myButton.addEventListener('click', theButtonAction);
yourButton.addEventListener('click', theButtonAction);
```

### Where Everybody Knows Your Name

In fact, this is _essential_ to another event-handling HoF: [`removeEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener). We need a reference to the original function if we want to remove it.

```javascript

const myButton = document.getElementById('myButton');
const theButtonAction = (event) => {
  console.log('This is a callback function!');
  console.log('We passed this to addEventListener.');
  console.log('You clicked', event.target);
  // Stopping the event handler needs the original function
  myButton.removeEventListener('click', theButtonAction);
};

myButton.addEventListener('click', theButtonAction);
```

Our earlier example has no reference to the callback, so we cannot use `removeEventHandler`. That handler accepts clicks as long as the element exists. In this newest example, the event handler only runs once because we are able to remove it.

## Timeouts and Intervals

Timeouts feature in a lot of coding examples because they are an easy way to cause asynchronous events or simulate more complex behavior. These are also higher-order functions that take a callback.

```javascript
setTimeout(() => console.log('After ~100 ms!'), 100);
setInterval(() => console.log('Every ~100 ms'), 100);
```

If you are unfamiliar with it, I use the tilde (~) to indicate _approximately_ 100 milliseconds because [timers & intervals are not precise](https://dev.to/oculus42/javascript-timers-intervals-5fnl).

## Array Operations

Many references to HoFs and Functional Programming in JavaScript talk about the Array prototype methods like [`.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) and [`.reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). These can be very useful for processing data.

```javascript
// Double
[ 1, 2, 3 ].map((value) => value * 2);
// [ 2, 4, 6 ]

// Sum
[ 1, 2, 3 ].reduce((accumulator, value) => value + accumulator, 0);
// 6

// Double and Sum (with chaining!)
[ 1, 2, 3 ]
  .map((value) => value * 2)
  .reduce((accumulator, value) => value + accumulator, 0);
// 12
```

These are also great examples of places where we can name and re-use functions.

```javascript
const double = (value) => value * 2;
const sum = (value1, value2) => value1 + value2;

[ 1, 2, 3 ].map(double);
// [ 2, 4, 6 ];

[ 1, 2, 3 ].reduce(sum, 0);
// 6;

// Double and Sum (with chaining!)
[ 1, 2, 3 ]
  .map(double)
  .reduce(sum, 0);
// 12
```

### Parameter Pairings

Knowing the parameters passed to callbacks for these HoFs is important to getting the correct response. The `sum` function won't work with `.map()` because [map doesn't provide two values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#parameters).

## React

Many of the React hooks are HoFs, like `useCallback`, `useEffect`, `useMemo`, and `useReducer`.

`useEffect` allows you to _write your own_ HoF as well, because the function you pass to `useEffect` can return a function which is called for cleanup.

```jsx
useEffect(() => {
  // This function is passed to useEffect
  doSomething();

  // Our function returns a function, too!
  return () => {
    cleanupSomething();
  }
});
```

Of course, these don't _have_ to be anonymous, though dependencies and consuming other hooks can complicate things.

```jsx
const someEffect = () => {
  doSomething();
  return cleanupSomething;
};

useEffect(someEffect);
```

## Samples & Suggestions

Try looking for anonymous callbacks like these in code samples and articles:

```javascript
// Anonymous timeout
setTimeout((result) => doSomething(result), 100);

// Anonymous promise chain
Promise.resolve(data)
.then((result) => doSomething(result));

// Anonymous event handler
foo.addEventListener((event) => handleEvent(event));

// Anonymous array operation
data.map((row) => reformatRow(row));
```

When you find them, ask yourself if you need that wrapper at all:

```javascript
// Not passing any arguments, so it's fine
setTimeout(doSomething, 100);

// Safe. Promises only pass one argument.
Promise.resolve(data)
.then(doSomething);

// Safe. Event handlers receive one argument.
foo.addEventListener(handleEvent);

// Depends. Does reformatRow accepts other arguments?
data.map((row) => reformatRow(row));
```

### Limitations

We can't eliminate a wrapper function in every case. Some HoFs like [`Array.prototype.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) provide _multiple arguments_ to their callback – `value, index, array` in this case – and some functions operate differently when they get more arguments. The classic example is `parseInt`, which accepts a _base_ as the second argument. If you pass it directly to `.map` it will receive the `index` as the second argument, producing unexpected outcomes.

```javascript
['9', '10', '11', '12', '13'].map(parseInt);
// [ 9, NaN, 3, 5, 7 ]

['9', '10', '11', '12', '13'].map((val) => parseInt(val));
// [ 9, 10, 11, 12, 13 ]
```

It's important to know what arguments are passed to your functions, and what arguments they accept.

## Feedback

Thanks for reading, and let me know what you thought. Do you have any tips or suggestions for developers learning to understand higher-order functions? Comment or post a link to other articles!