Array methods and iterables - Stepping up your JavaScript game
Tags: javascript, node, arrays, ecmascript
Published: 2019-03-20 13:28:19

> Originally posted at [michaelzanggl.com](https://michaelzanggl.com/articles/array-methods-and-iterables). Subscribe to [my newsletter](https://michaelzanggl.com/) to never miss out on new content.

Today I want to introduce some array methods that help you step up your JavaScript game.

For all examples, let's imagine we have the following variable declaration
```javascript
let users = [
  {id: 1, name: 'Michael', active: true, group: 1 }, 
  {id: 2, name: 'Lukas', active: false, group: 2 }
]
```

Throughout this article you will understand how to turn this

```javascript
const activeUsernames = []

users.forEach(user => {
  if (user.active) {
    activeUsernames.push(user.name)
  }
})
```

into this

```javascript
const activeUsernames = users
  .filter(user => user.active)
  .map(user => user.name)
```

as well as a lot more.

We want to focus on four objectives when it comes to code improvements
- avoiding temporary variables
- avoiding conditionals
- be able to think of your code in steps
- reveal intent

We will highlight the most important methods on the Array prototype (leaving out basic array manipulation like `push`, `pop`, `splice` or `concat`) and hopefully you will find scenarios where you can apply these instead of the following usual suspects.

**for loop**
```javascript
for (let i = 0; i < users.length; i++) {
    //
}
```

**Array.prototype.forEach**
```javascript
users.forEach(function(user) {
    //
}
```

**ES6 for of Loop**
```javascript
for (const user of users) {
    //
}
```

---

One more thing before we get started!

If you are unfamiliar with ES6 arrow functions like:

```javascript
users.map(user => user.name)
```

I recommend you to take a look at those first.
In summary, the above is very similar, and in this case, the same as

```javascript
users.map(function(user) {
   return user.name
})
```

--- 

## Array.prototype.filter

Let's say we want to find all users that are active. We briefly looked at this in the introduction of the article.

```javascript
const activeUsers = []

users.forEach(user => {
  if (user.active) {
    activeUsers.push(user)
  }
})
```

If we look back at the four objectives we set before, it is very obvious that this is violating at least two of them.
It has both **temporary variables** as well as **conditionals**.

Let's see how we can make this easier.

```javascript
const activeUsers = users.filter(user => user.active)
```

The way `Array.prototype.filter` works is that it takes a function as an argument (which makes it a higher order function) and returns all users that pass the test. In this case all users that are active.

I think it is safe to say that we were also able to reveal our intent. `forEach` can mean anything, it might save to the database, etc. while `filter` does what the name suggests.

Of course you can also use `filter` on a simple array.
The following example would return all animals starting with the letter a.
```javascript
['ape', 'ant', 'giraffe'].filter(animal => animal.startsWith('a'))
```

A use case I also see often is removing items from an array. Imagine we delete the user with the id 1. We can do it like this
```javascript
users = users.filter(user => user.id !== 1)
```

Another use for filter is the following
```javascript
const result = [true, 1, 0, false, '', 'hi'].filter(Boolean) 
result //? [true, 1, 'hi']
```

This effectively removes all falsy values from the array. There is no magic going on here. `Boolean` is a function that takes an argument to test whether it is truthy or not. E.g. `Boolean('')` returns false, while `Boolean('hi')` returns true. We simply pass the function into the `filter` method, so it acts as our test.

## Array.prototype.map

It often happens that we have an array and want to transform every single item in it. Rather than looping through it, we can simply map it.
Map returns an array with the same length of items, it's up to you what to return for each iteration.

Let's create an array that holds the usernames of all our users.

Traditional loop

```javascript
const usernames = []

users.forEach(user => {
  usernames.push(user.name)
})
```

Mapping it

```javascript
const usernames = users.map(user => user.name)
```

We avoid *temporary variables* and *reveal intent* at the same time.

## Chaining
What is great about these higher order functions is that they can be chained together. `map` maps through an array and returns a new array. `filter` filters an array and returns a new array. Can you see a pattern? With this in mind, code like the following becomes not only possible but very readable

```javascript
const activeUsernames = users
  .filter(user => user.active)
  .map(user => user.name)
```

And with this we complete our final objective `to think in steps`. Rather than thinking out the whole logic in your head, you can do it one step at a time. Think of the example we had at the very start.

```javascript
const activeUsernames = []

users.forEach(user => {
  if (user.active) {
    activeUsernames.push(user.name)
  }
})
```

When you read this for the first time, in your mind the process would go somewhat like
- initialize an empty array
- loop through all users
    - if user is active
        - push to the array from the beginning
            - but only the name of the user
- repeat

With the refactored method it looks more like this
- get all active users
- create new array of the same size
    - that only hold their username

That's a lot easier to think and reason about.

---

There are many more interesting methods available. Let's check out some more.

## Array.prototype.find

The same way `filter` returns an array with all the items that pass the test, `find` returns the first item that passes the test.

```javascript
// returns user with id 1
users.find(user => user.id === 1)
```

> `Array.prototype.findIndex` works the same way but it returns the index instead of the item

For arrays that don't require deep checking there is no need to have the overhead of an extra function, you can simply use `includes` and `indexOf` respectively.

```javascript
['a', 'b', 'c'].includes('b') //? true
['a', 'b', 'c'].indexOf('a') //? 0
['a', 'b', 'c'].includes('d') //? false
['a', 'b', 'c'].indexOf('d') //? -1
```

## Array.prototype.some

Returns true if at least one test passes. We can use this when we want to check if at least one user in our array is active.

Traditional solution using for loop
```javascript
let activeUserExists = false
for (let i = 0; i < users.length; i++) {
  if (users[i].active) {
    activeUserExists = true
    break
  }
}
```

Solution using `some`
```javascript
users.some(user => user.active)
```

## Array.prototype.every
Returns true if all items pass the test. We can use this when we want to check whether all users are active or not.

Traditional solution using for loop
```javascript
let allUsersAreActive = true
for (let i = 0; i < users.length; i++) {
  if (!users[i].active) {
    allUsersAreActive = false
    break
  }
}
```

Solution using `every`
```javascript
users.every(user => user.active)
```

## Array.prototype.reduce
If none of the above functions can help you, reduce will! It basically boils down the array to whatever you want it to be. Let's look at a very simple implementation with numbers. We want to sum all the numbers in the array. In a traditional forEach loop it would look like this:

```javascript
const numbers = [5, 4, 1]
let sum = 0
numbers.forEach(number => sum += number)
sum //? 10
```

But the reduce function takes away some of the boilerplate for us.

```javascript
const numbers = [5, 2, 1, 2]
numbers.reduce((result, number) => result + number, 0) //? 10
```

`reduce` takes two arguments, a function and the start value. In our case the start value is zero. If we would pass 2 instead of 0 the end result would be 12.

So in the following example

```javascript
const numbers = [1, 2, 3]
numbers.reduce((result, number) => {
    console.log(result, number)
    return result + number
}, 0)
```
the logs would show:

- 0, 1
- 1, 2
- 3, 3

with the end result being the sum of the last two numbers 3 and 3, so 6.

Of course we can also reduce our array of objects into, let's say a hashmap.

Grouping by the `group` key, the resulting hashMap should look like this
```javascript
const users = {
  1: [
    { id: 1, name: 'Michael' },
  ],
  2: [
    { id: 2, name: 'Lukas' },
  ],
}
```

We can achieve this with the following code
```javascript
users.reduce((result, user) => {
  const { group, ...userData } = user
  result[group] = result[group] || []
  result[group].push(userData)
  
  return result
}, {})
```

- `const { group, ...userData } = user` takes the `group` key from the user, and puts the remaining values inside `userData`.
- With `result[group] = result[group] || []` we initialize the group in case it doesn't exist yet. 
- We push `userData` into the new group
- We return the new result for the next iteration

---

## Using this knowledge on other iterables and array-like objects

Do you remember this from before?

**for loop: works on array-like objects**
```javascript
for (let i = 0; i < users.length; i++) {
    //
}
```

**Array.prototype.forEach: method on the array prototype**
```javascript
users.forEach(function(user) {
    //
}
```

**ES6 for of Loop: works on iterables**
```javascript
for (const user of users) {
    //
}
```

Did you realize how significantly different the syntax of the `forEach` and the two `for` loops are?

Why? Because the two `for` loops do not only work on arrays. In fact they have no idea what an array even is.

I am sure you remember this type of code from your CS classes.

```javascript
const someString = 'Hello World';
for (let i=0; i < someString.length; i++) {
    console.log(someString[i]);
}
```

We can actually iterate through a string even though it is not an array.

This kind of `for` loop works with any "array like object", that is an object with a length property and indexed elements.

The `for of` loop can be used like this

```javascript
const someString = 'Hello World';
for (const char of someString) {
    console.log(char);
}
```

The `for of` loop works on any object that is iterable.

To check if something is iterable you can use this rather elegant line `Symbol.iterator in Object('pretty much any iterable')`.

This is also the case when dealing with the DOM. If you open the dev tools right now and execute the following expression in the console, you will get a nice red error.

```javascript
document.querySelectorAll('div').filter(el => el.classList.contains('text-center'))
```

Unfortunately `filter` does not exist on iterable DOM collections as they are not Arrays and therefore don't share the methods from the Array prototype. Want proof?

```javascript
(document.querySelectorAll('div') instanceof Array) //? false
```

But it is an array like object

```
> document.querySelectorAll('.contentinfo')

    NodeList [div#license.contentinfo]
        0: div#license.contentinfo
        length: 1
        __proto__: NodeList
```


and is also iterable

```javascript
Symbol.iterator in Object(document.querySelectorAll('div')) //? true
```

If we want to use our newly trained Array knowledge on let's say iterable DOM collections, we have to first turn them into proper arrays.

There are two ways of doing it.
```javascript
const array = Array.from(document.querySelectorAll('div'))
```

or

```javascript
const array = [...document.querySelectorAll('div')]
```

I personally prefer the first way as it provides more readability.

## Conclusion

We went through the most important methods on the array object and took a look at iterables. If we look back to the objectives we set at the start, I think it is safe to say that we at least accomplished
- thinking in steps
- avoiding temporary variables
- avoiding conditionals

But I am not fully satisfied with `reveal intent`.

While
```javascript
const usernames = users.map(user => user.name)
```

is definitely much more readable than

```javascript
const usernames = []

users.forEach(user => {
  usernames.push(user.name)
})
```

wouldn't

```javascript
const usernames = users.pluck('name')
```

be even nicer?

In the next article, we will take a look at subclassing arrays, so we can provide exactly such functionality. It will also be a great entry point for unit testing with Node.js, so stay tuned.

P.S. if you are a fan of Laravel, please take a look at [Laravel Collections](https://www.youtube.com/watch?v=crSUWtRYw-M&t=639s).

---

If this article helped you, I have a lot more tips on simplifying writing software [here](https://michaelzanggl.gumroad.com/l/intent-driven-development).