Building Iterators
Tags: javascript, beginners, programming, node
Published: 2022-04-04 01:36:06

As programmers, one of the very first thing we learn is "[the loop](https://en.wikipedia.org/wiki/Control_flow#Loops)". There is always an array or list or collection which needs to be looped through, an object or map or dictionary whose keys and values require traversing. Iterations are key programming concept.

Arrays and Maps are collections of things and it should to be defined explicitly prior to iterating. You can start out with an empty array or a declaration and you can push items to it. Example: 

```js
const things = ['headphone', 'keyboard', 'mouse']
const friends = {
	tomo: 'nuxt expert',
	jt: 'writes nasty sqls',
	deco: 'the leader',
	van: 'php guru'
} 

things.push('cables')

// one of the ways of iterating arrays
for (const thing of things) {
  console.log(thing)
}

// iterate the key of objects
for (const name in friends) {
  console.log(`${name} - ${friends[name]}`)
}

```

There are many ways of iterating over an array or an object. To name a few:
- `for(const i = 0; i < arr.length; i+=1)` 
- `for...of` [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of)
- `for...in` [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in)
- `while()` [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while)
- `Array.forEach` [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
- `Array.map` [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- `Array.filter` [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
- `Array.reduce` [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)

One thing about arrays or objects(Map, Set etc) is that you kind of know what you are getting. You could push things into an array but you know what you pushed. Its not dynamic. If something is in array, its there for good until you remove it. Also, its taking up that space in the memory. 

## Iterator protocol
What if you had a dynamic array that calculated what `value` you get in the `next` iteration? What if that worked based on a formula that you've built? You need to use iterator pattern. You'll notice that its very simple to implement. Its a protocol that is well known among JS programmers and also followed in other languages too. An iterator is an object that has `next()` method. Calling `next()` function on the object gives us the iterator result which is an object with two properties - `done` which is a boolean to hold the status of the iterator and `value` to hold whatever you want to return. Let's build a simple range iterator. This range iterator will allow us to create a range of numbers by providing a start, end and step.

```js 
// iterator protocol: an agreed interface
function numberRangeIterator(start, end, step) {
  let index = start
  return {
    next() {
      if (index > end) {
        return { done: true, value: 'thanks for using me' } // value is optional here but you can use it to return meta info
      }
      const value = index
      index += step
      return { done: false, value }
    }
  }
}

const iterator = numberRangeIterator(3, 30, 3)
let iteratorResult = iterator.next()
while (!iteratorResult.done) {
  console.log(iteratorResult.value)
  iteratorResult = iterator.next()
}
```

You see? Its very simple and yet powerful. Two things to note:
- the `next` function should return and object with `done: true` to indicate that there are no more elements. But it's not mandatory, you can have an iterator that runs forever!
- you can have `done: false` or return just `{value}` and above code will just work fine.

```js
function randomNumberIterator() {
  return {
    next() {
      return { done: false, value: Math.random() }
    }
  }
}

const rIterator = randomNumberIterator()
let rIteratorResult = rIterator.next()
while (!rIteratorResult.done) {
  console.log(rIteratorResult.value)
  rIteratorResult = rIterator.next()
}
```

While I cannot think of when you'd use the iterator above, I just wanted to demonstrate an iterator that can generate random numbers infinitely.

## Iterable protocol
Iterable protocol goes one step further by defining a standard within JS language for any object to return an iterator. An `iterable` is an object that implements an iterator method called `[Symbol.iterator]`. The best thing about using iterables over the iterator which we talked about above is that it allows us to use JS native apis for looping over the array such as `for...of`. Let's build our `numberRangeIterator` as an iterable.

```js
class NumberRange {
  constructor(start, end, step) {
    this.start = start
    this.end = end
    this.step = step
  }
  // for an object/class to classify as iterable
  // it has to implement [Symbol.iterator]
  [Symbol.iterator]() {
    let index = this.start
    return {
      next: () => {
        if (index > this.end) {
          return { done: true }
        }
        const value = index
        index += this.step
        return { value }
      }
    }
  }
}

const myRange = new NumberRange(3, 30, 3)

for (const num of myRange) {
  console.log(num)
}
```

It was almost the same amount of code to define the iterable class and we reused most of our code. However, the beauty is in the way we consume the iterator. Using `for...of` makes it look so clean and concise. I prefer this over the `while` loop above. But it doesn't stop here. There are other ways you can consume this iterable. You can use it with spread operator.
```js
const myRange2 = new NumberRange(5, 20, 4)
console.log(...myRange2) // prints 5 9 13 17
```

Or, destructure and assign it 
```js
const myRange2 = new NumberRange(5, 20, 4)

const [first, second, third] = myRange2
console.log(first, second, third) // prints 5 9 13
```

There are other JS built-in APIs that accept iterables where you can pass your iterables such as `Array.from(iterable)`, `Set([iterable])`, `Promise.all(iterable)` and even `stream.Readable.from(iterable)`.

[Read  more about iterators here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators). You can pretty much treat it like your regular array but dynamic in nature and it will calculate your values only when it needs to. Things get a bit hairy though, when you start getting into the territory of async iterators but that is for another day.