JavaScript beyond Simple Functions
Tags: programming, javascript
Published: 2024-02-08 21:34:12

Wether you are new to programming or not, functions are at the core of many programming languages, especially JavaScript. Simple functions encapsulate logic, execute actions according to parameters and allow for returning some result or not. But JavaScript enables you to expand the trivial use of functions with some features.

In this article we will explore how to go beyond the use of simple functions, understanding the concept and usage of higher order functions, closures and generators.

## Simple Functions

A function is a "set of statements that perform a task or calculates a value"[¹](#references). We can see below an example of a function in JavaScript:

```JavaScript
function multiply(numberA, numberB, numberC) {
    return numberA * numberB * numberC;
}
```

This function receives three parameters: `numberA`, `numberB` and `numberC`. It multiplies them and returns the result. This is the standard syntax for creating functions in JavaScript, but we could also write using the Arrow function syntax:

```JavaScript
const multiply = (numberA, numberB, numberC) => numberA * numberB * numberC;
```

Arrow functions provide a cleaner syntax, which in some cases may improve readability. Just keep in mind that this syntax has some limitations[²](#references).

### Pure Functions and Side Effects

You may have already heard the term "Pure Function", but what is that exactly?

Due to the flexibility that functions provide, nothing would prevent us from creating the following function:

```JavaScript
let someValue = 7;

function sum(a, b) {
    someValue = a;
    return a + b;
}
```

By looking at the function name, you would expect it to return the sum of `a` and `b`, but it is also changing a value from a variable that was not passed as parameter. We refer to this as a **side effect** of the function.

In order to be considered pure[³](#references), a function must comply with the following rules:

1. Given the same inputs, the function must always return the same output;
2. The function must not have any side effects.

We've already seen an example of a pure function, the one used to multiply three values. That function does not act on anything outside of it's scope and always returns the same values for the same input (for example, given the values 1, 2, and 3, it will always return 6).

It is easier to reason about and debug pure functions, but sometimes we actually want to have impure functions, such as one that returns a different value every time it is called, as we will see later in the article.

## Higher order functions

Functions may receive different types of parameters. In the examples we've seen so far, we are always passing numbers as parameters. But functions can receive several other types, such as strings, arrays, booleans, objects and even **functions**.

You read that right: you can pass a function as a parameter to another function. Let's see how that is done:

```JavaScript
function add2(number) {
    return number + 2;
}

function subtract2(number) {
    return number - 2;
}

function calculate(a, b, fn) {
    let c = a + b;
    return fn(c);
}

calculate(3, 4, add2); // 9
calculate(3, 4, subtract2); // 5
```

The `add2` function simply adds 2 to any number it receives, while the `subtract2` subtracts 2 from its parameter.

The calculate function does 2 things: first it sums the values of `a` and `b`. Then the result of this sum (`c`) is passed to the function it receives as `fn`, to be returned at the end. This provides the flexibility of performing different types of calculations according to the provided function.

After the function definitions we execute `calculate` twice, with the same numbers: 3 and 4. The first time we are passing the `add2` function as parameter, and the result is `3 + 4 = 7 => 7 + 2 = 9`, therefore it returns `9`.

The second time we pass the `subtract2` function as parameter, and the result is `3 + 4 = 7 => 7 - 2 = 5`, therefore it returns `5`.

A function that receives another function as parameter is called a **Higher Order Function**[⁴](#references), and it is a very useful feature of JavaScript. One popular higher order function in JavaScript is the `sort()` function.

```JavaScript
const users = [
    { id: 1, age: 21, name: 'John Doe' },
    { id: 2, age: 26, name: 'Mark Johnson' },
    { id: 3, age: 19, name: 'Jack Smith' },
    { id: 4, age: 25, name: 'Mary Doe' },
    { id: 5, age: 30, name: 'Abigail David' },
    { id: 6, age: 29, name: 'Stuart Fox' }
];

const compareByAge = (a, b) => {
    if (a.age < b.age)
        return -1;

    if (a.age > b.age)
        return 1;

    return 0;
}

const compareByLastName = (a, b) => {
    const lastNameA = a.name.split(' ')[1];
    const lastNameB = b.name.split(' ')[1];

    return lastNameA.localeCompare(lastNameB);
}

users.sort((a, b) => a.id > b.id ? -1 : 1); // users ordered by descending id
users.sort(compareByAge); // users ordered by age 
users.sort(compareByLastName); // users ordered by last name
```

The `Array.prototype.sort()` function is a higher order function that can have a comparer function passed as parameter[⁵](#references). It can sort an array in different ways, depending on the function it receives. In our example, the first time we called `sort()` we passed a simple arrow function to sort the items by id in descending order. The second time we passed a function that compares the ages, and sorts by age in ascending order. The third time we compared last names, using the built-in string function `localCompare()`[⁶](#references).

## Closures

Functions have scopes for accessing variables. In one of our examples, we were able to change a value that was defined outside of our function. Closures follow the same idea, which means that it provides access to variables within its scope.

According to MDN, a closure is "the combination of a function and the lexical environment within which that function was declared"[⁷](#references). Let's see how we could make use of a Closure with an example.

```JavaScript
function sumAllMaker() {
    let total = 0;

    function sumAll(a, b) {
        total += (a + b);
        return total;
    }

    return sumAll;
}

const totalSum = sumAllMaker();

totalSum(1, 2); // 3
totalSum(1, 2); // 6
totalSum(3, 1); // 10
```

To understand what is going on with our example, let's start by looking at the inner function, called `sumAll()`. This function receives 2 parameters, and sums them. The result is then aggregated to `total`, which is a variable outside of `sumAll()`. The variable was declared in the outer function, but just like we could previously access global variables, here we have access to variables in the outer function. So, subsequent calls to this function will always update the value stored in total.

Now let's look at the outer function: `sumAllMaker()`. It's starts by assigning 0 to our variable `total`. Then it defines the function `sumAll()` which is where the logic to update the total resides, and at the end it returns a reference to the function `sumAll`. That is important because now we can declare a variable, and by assigning `sumAllMaker()` to it we get access to the `sumAll` function.

In other words, now that we declared `const totalSum = sumAllMaker()`, calling `totalSum()` is the same as calling the `sumAll()` function.

Finally we call the function 3 times. Notice that the first time we call it with the values 1 and 2, it returns 3. The second time we call it with the same values, it returns 6. That is because the total is being kept and updated every time we call the function.

That is our closure in action! We called it once more, and it returned 10. Any subsequent calls will continue to update total and return it appropriately. Notice that this closure cannot be considered a pure function, because calling the same function twice with the same parameters return different results.

## Generator Functions

Imagine that you are creating objects and you want each of them to have a different id. You don't want to manually assign ids, instead you would like to create a function to generate them.

One way to solve it would be to create a random[⁸](#references) number generator.

```JavaScript
function getRandomInteger(min, max) {
    return Math.floor(Math.random() * (max - min) + min);
}

getRandomInteger(100, 999); // 926
getRandomInteger(100, 999); // 508
```

Just as an exercise, would you be able to explain wether `getRandomInteger()` is a pure function, and why?

Using this function would not work for our scenario because nothing guarantees that the function would return unique numbers. At some point I could get the same id again, since it's random, there is no control on which number is returned.

As we've seen previously, we could use a closure to solve that.

```JavaScript
function idMaker() {
    let id = 100;
    return function getId() { return ++id; }
}

const getId = idMaker();
getId(); // 101
getId(); // 102
getId(); // 103
```

That's awesome! Now we know how to use a closure to generate ids. But there is another way to achieve this, by using a generator.

A generator function uses a special notation, including an asterisk after the function keyword: `function*`[⁹](#references). This type of function returns an object that can be iterated upon. Think of it as a function that can be paused and resumed. Within the function, the `yield` statement is what "pauses" the function, returning some value. Subsequent calls to the generator will resume from the last `yield` until the next one, and so on.

Let's see an example using a simple generator that returns the vowels.

```JavaScript
function* generateVowels() {
    yield 'a';
    yield 'e';
    yield 'i';
    yield 'o';
    yield 'u';
}

const vowels = generateVowels();
vowels.next().value; // 'a'
vowels.next().value; // 'e'
vowels.next().value; // 'i'
vowels.next().value; // 'o'
vowels.next().value; // 'u'
vowels.next().value; // undefined
```

As you can see, every time we call the `next()` function and get the value, a subsequent `yield` is executed, until the iterator completes. After that, any further execution returns `undefined`.

Considering what we've seen so far, would you be able to create a generator that returns ids from 101 until 999? If you feel like it, pause reading and go try it for yourself! Otherwise, the answer follows below.

```JavaScript
function* generateIds() {
    let id = 100;
    while (id < 999) { yield ++id; }
}

const ids = generateIds();
ids.next().value; // 101
ids.next().value; // 102
ids.next().value; // 103
```

## Conclusion

JavaScript is a powerful language, and it provides flexible ways of working with functions. Try to find opportunities in your routine to integrate some of these options, and go beyond the use of simple functions.

---

### References
1. [Functions - MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions)
2. [Arrow function expressions - MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
3. [What is a Pure Function in JavaScript - freeCodeCamp](https://www.freecodecamp.org/news/what-is-a-pure-function-in-javascript-acb887375dfe/)
4. [Higher Order Functions in JavaScript – Explained with Practical Examples - freeCodeCamp](https://www.freecodecamp.org/news/higher-order-functions-in-javascript-explained/)
5. [Array.prototype.sort() - MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
6. [String.prototype.localeCompare() - MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare)
7. [Closures - MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)
8. [Math.random() - MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random)
9. [function* - MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*)