DEV Community

Linas Spukas
Linas Spukas

Posted on

Higher-Order Functions In JavaScript

In JavaScript, functions are treated as first-class objects. That means they can be stored as any other values in objects or arrays, passed around as arguments, or returned from other functions.
Higher-order functions are a functional programming pattern when functions are being passed as arguments to other functions or returned as a result.
The example below illustrates the HoF pattern when one function takes another function as an argument and returns composed data:

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

function process(data, callback) {
    for (let i = 0; i < data.length; i += 1) {
        callback(data[i]);
    }
}

process([1, 2, 3], log); // prints 1; 2; 3;

Function process here is higher-order, it takes data array, loops through all items and calls log function on all of them.

Array HoF

The best example of HoF are methods, built-in in Array object. If you are confused about name methods, they are simply functions stored in an object as properties. And in JavaScript, everything is an object, including arrays.

The most common used array HoF are:

  • forEach()
  • map()
  • filter()
  • reduce()

Let's take Array.prototype.map() method as an example.
The method returns a new array with the result, populated by calling a function on each element of the array. Tham means map() function takes another function (callback) as an argument and runs it on each item of the array.

const numbers = [1, 2, 3];

// pass unonymouse function
numbers.map(function(item) {
    return item * 2;
}); // [2, 4, 6]


// or extract the callback into a named function
function double(item) {
    return item * 2;
}

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

Why It Is Useful?

The best part of higher-order functions is composability. It gives you the ability to combine functions and operate them in a sequence. For example, you could compose HoF in a pipeline with array methods:

const numbers = [1, 2, 3];

numbers
 .map((n) => n * 2) // it will return [2, 4, 6]
 .filter((n) => n % 4) // it will filter out number that divides by 4
 .reduce((a, b) => a + b); // return 6 - sum of the array items

Or you could write your own HoF that takes any amount of callback functions and runs against the data:

function compose(...fns) {
    return function(arr) {
        return fns.reduceRight((acc, fn) => fn(acc), arr);
    }
}

function pow2(arr) {
    return arr.map(v => v * v)
}

function filterEven(arr) {
    return arr.filter(v => v % 2);
}

const pipe = compose(filterEven, pow2);

pipe([1, 2, 3, 4]) // [1, 9];

The function compose takes two functions: pow2 and filterEven and returns an anonymous function that reduces passed data (an array of numbers) by applying callback functions from right to left. This way you can pass any number of callbacks, create different pipeline structures and pass various data.

As callbacks are pure functions (returns the same output, given the same input) they are easier to test, which reduces the amount of bugs and side effects.

Conclusion

We learned that functions are values and it defines how we are treating them in JavaScript. While the higher-order concept describes how we are using them. Most of the Array methods are HoF, as they take other functions as arguments. This concept lets to compose functions into pipelines, which makes code to read easier and less buggy.

Top comments (0)