Higher-Order Functions In JavaScript

oluwatobi_

Oluwatobi Adedeji

Posted on January 30, 2023

Higher-Order Functions In JavaScript

One of the sweet things I really enjoy about JavaScript is the fact that it is a multi-paradigm programming language, you can
apply different paradigms of programming; object-oriented, functional, event-driven, etc
Although most times, we tend to treat JavaScript as a functional programming language based on our preference, one of the things
When dealing with JavaScript as a functional programming language, we need to note that we cannot be mutating states or change values assigned to variables.
A way we can achieve this is by using higher-order functions.

Before we begin to talk about higher-order functions in JavaScript let's understand some basic concepts about JavaScript that
will make reading this article easy to follow. Concepts like:

  • declaring functions in JavaScript
  • function arguments in JavaScript
  • function return type
  • functions in JavaScript as a first-class citizen

And later in this article, we will be touching:
-higher-order functions in JavaScript
-examples of higher-order functions in JavaScript

  • use cases of high-order functions in solving problems in JavaScript
  • writing your own higher-order functions in JavaScript
  • Let's also play around with writing some built-in higher-order functions in JavaScript.

Let's dive in,

Declaring Functions in JavaScript:

Declaring functions in JavaScript is simply done by giving a function name, a function body, and a return value(if required)

function firstFunction
{
console.log("our first function")
}
//So when we want to make use of the function :

firstFunction()
Enter fullscreen mode Exit fullscreen mode

We just wrote a basic function, let's continue:
Let's write a function that can sum up two numbers; we will need two arguments, parameters are used when a function is declared but arguments are used when the function is called

// Definition:
function sumTwoNumbers(num1,num2)
{
  const sum = num1 + num2
   return sum
}

//Let's call the function:
//Here we will pass the parameters which are in this case the two //numbers we want to add together
console.log(sumTwoNumbers(3,7)
// The result we have is 10

Enter fullscreen mode Exit fullscreen mode

And the result will always be consistent and correct as long as nothing goes wrong in the function block

In the function we just wrote to sum up two numbers, we are actually returning the sum of the two numbers.. One thing we need to take note of is that
whenever we have a return statement in a function, the function stops executing at that point, we will make use of this later..

Functions in JavaScript can be treated as first-class citizens, what I am saying here is that functions can be treated as a variable in JavaScript, let me explain this
using the previous functions we have written.

//The first function we wrote can be written as :
const firstFunction = ()=>{
console.log("our first function")
}
Enter fullscreen mode Exit fullscreen mode

You notice we are declaring our function as a variable here, let's also re-write the second function

const sumTwoNumbers = (num1,num2) => num1 + num2

Enter fullscreen mode Exit fullscreen mode

This looks shorter right? Let me explain what is happening here, instead of writing long lines of code, if you want to write a function that does not require
multiple lines of logic, you also notice we don't explicitly write a return statement but as far as there is no curly brace after the big fat arrow, the function
returns the value of _num1 + num2
_

We can now safely move on to higher-order functions in JavaScript:

Higher-order functions are functions that take a function as an argument and or or return a function

There are various higher-order functions in JavaScript, we will only be touching a few of them.

setTimeOut:
setTimeOut takes in an anonymous function as its first parameter and the time in milliseconds as the second parameter.

Let's say we want to print out the current date and time after 4 seconds of running the snippet:

setTimeout(()=>{
    console.log(new Date)
},4000)
Enter fullscreen mode Exit fullscreen mode

Array.map() :
a map function is used to map through an array, but unlike the regular forEach or for, map function returns an array

For Example, let's say we want to add two to every number in an array of numbers

If we want to do this with our regular forEach, we will do it this way:

const numArr = [1,2,3,4,5,6,7,8,9,10]
const newNumArr = []

numArr.forEach((num)=>{
    newNumArr.push(num + 2)
})

console.log(newNumArr)
Enter fullscreen mode Exit fullscreen mode

We get the result we want but, remember if we are doing functional programming at its core, we wouldn't want to be mutating state..
This is now where we can make use of a higher-order function:

const numArr = [1,2,3,4,5,6,7,8,9,10]
const newNumArr = numArr.map(num => num + 2)
console.log(newNumArr)
Enter fullscreen mode Exit fullscreen mode

Apart from that the code is more readable and precise, you should also notice that we do not have reason to reassign or mutate any variable..
That's one of the reasons we use higher-order function

Array.reduce()

Let's say we want to want to add all the numbers in an array of numbers, using our forEach loop, we would write something like this:

const numArr = [1,2,3,4,5,6,7,8,9,10]
let accumulatedSum = 0

numArr.forEach(num => {
    accumulatedSum += num

}    
)
console.log(accumulatedSum)
Enter fullscreen mode Exit fullscreen mode

Though forEach is still a higher-order function but remember we want to avoid mutating states..

We can use the reduce() function to achieve what we want :

const numArr = [1,2,3,4,5,6,7,8,9,10]

const arrSum = numArr.reduce((accumulator,currentValue)=> accumulator + currentValue,0)

console.log(arrSum)
Enter fullscreen mode Exit fullscreen mode

In case you have not used the reduce function before, let me explain;

The reduce() method executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.

The first time that the callback is run there is no "return value of the previous calculation". If supplied, an initial value may be used in its place. Otherwise, the array element at index 0 is used as the initial value and iteration starts from the next element (index 1 instead of index 0).

callbackFunction
A function to execute for each element in the array. Its return value becomes the value of the accumulator parameter on the next invocation of callbackFunction. For the last invocation, the return value becomes the return value of reduce().

The function is called with the following arguments:

accumulator
The value resulting from the previous call to callbackFunction. On the first call, initialValue if specified, otherwise the value of array[0].

currentValue
The value of the current element. On the first call, the value of array[0] if an initialValue was specified, otherwise the value of array[1].

currentIndex
The index position of currentValue in the array. On the first call, 0 if initialValue was specified, otherwise 1.

array
The array reduce() was called upon.

initialValue Optional
A value to which accumulator is initialized the first time the callback is called. If initialValue is specified, callbackFunction starts executing with the first value in the array as currentValue. If initialValue is not specified, accumulator is initialized to the first value in the array, and callbackFunctiion starts executing with the second value in the array as currentValue. In this case, if the array is empty (so that there's no first value to return as accumulator), an error is thrown.

So the complete syntax for reduce is

Array.reduce((accumulator,currentValue,currentIndex)=>{

// write your reducer logic here

},initialValue)
Enter fullscreen mode Exit fullscreen mode

We can perform a few more complex tasks using reduce():

Let's say we have an array containing the objects of the student's name, age, and state of origin and we want to have a method that we can use to group the student list
by any of the three fields(name, age or state of origin)

const students = [
    { name: "Mike", age: 10,stateOfOrigin:"Niger" },
    { name: "Max", age: 12,stateOfOrigin:"Oyo" },
    { name: "Jane", age: 15,stateOfOrigin:"Abia" },
    { name: "Adora", age: 13,stateOfOrigin:"Benue" },
    { name: "Aishat", age: 14, stateOfOrigin: "Rivers" },
    { name: "Abu", age: 15,stateOfOrigin:"Adamawa" },
    { name: "Abraham", age: 14,stateOfOrigin:"Adamawa" },
    { name: "Tunmise", age: 15,stateOfOrigin:"Oyo" },
  ];

  const groupBy = (studentData,groupByProperty) => {
    return studentData.reduce((accumulated,currentObj)=>{
        const key = currentObj[groupByProperty]
        const currentGroup = accumulated[key] ?? []

        return {...accumulated,[key]:[...currentGroup,currentObj]}
    },{})
  }

  const groupedStudentByState = groupBy(students,"stateOfOrigin")

  console.log(groupedStudentByState)
Enter fullscreen mode Exit fullscreen mode

In the snippet above, we have an array of student data and we also have a function to group the data we have.

In the groupBy function, we are returning the grouped data directly, the function takes in two arguments; the studentData and the groupByProperty, We are using a
reduce() function on the studentData; following the syntax for reduce(), we have a callback function that takes in accumulated data, the current value as parameters,
we also have the initial value which is an empty object { }.

Making your own higher-order function in JavaScript
Let's say you are working on a project with different folders,for example, express js and you have the models, controllers, routes, etc folders
When you write a function and you want to track if an error occurred or not when the function is called, you can pass in a callBack into the function.

For Example

// models/

const insertDataToDB = (...args,callBackFn) =>
{
  db.query(...,[],(err,res)=>{
    if(err){
    callBackFn(err,null)    
}
if(res){
   callBackFn(null,res)
}

})
}

export {insertDataToDB}

// controller/
import {insertDataToDB} from "../models/..."

const insertData = (req,res)=>{
   insertDataToDB(req.body,(error,data)=>{
    if(error){
    res.status(500).json({...error})    
}

if(data){
res.status(200).json({...data})
}

})
}

Enter fullscreen mode Exit fullscreen mode

In the insertToDB function, we passed in a callBack into the function so when we call the function in the controller, we can check if an error occured or not and we can respond to the request.

I really hope you have learned a lot through this article, check up some of the code snippets in this article on GitHub do well to like and share this post and also follow me on Twitter and LinkedIn to get more updates from me, thank you see you soon.

Linkedln
Twitter

💖 💪 🙅 🚩
oluwatobi_
Oluwatobi Adedeji

Posted on January 30, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related