Iterables and Iterators in JavaScript - II
Tags: javascript, webdev, programming
Published: 2021-04-01 18:59:06


In our last article we looked at some built in iterables in JavaScript, we touched arrays, maps and sets. In this article we are going to look at how we can create an our own custom iterables and iterators.

Remember how we said that all iterables have a `[Symbol.iterator]()` function, this is the standard iterator protocol that allows us to loop through the properties of the object. There's something i'm guilty of,  i didn't tell you that strings are iterables and i apologize for that, we can create an  iterator from a string. How ?? Keep reading.

## What are iterators 

An iterator is just a pointer for going over the elements of a data structure. An iterator must have a next() function that returns an object that contains two properties; 

*  `value` which holds the current value the pointer is at. 

* And a boolean `done` that returns false except there is no more items in the iterator, then it returns true.

  The function of the next method is to enables us go over the elements of the data structure, it also tracks the position of the current item the pointer is at. 

### How do they differ from iterables ?

Iterables are simply objects that implement an iteration protocol, i.e they have a `[Symbol.iterator]()` function; so how are they related to iterators? An iterator is usually returned from the iteration protocol of an iterable.

## Creating an Iterator from a string

To create an iterator from a string, we create a variable and set the value of the variable to be equal to the iteration protocol of the string from whose value we want to create the iterator.

```javascript
const someString = "string" 
const myIter = someString[Symbol.iterator]()

console.log(myIter)
//StringIterator {}

for (v of myIter) {
  console.log(v)
}
// s, t, r, i, n, g
```

We have created an iterable from a string, using the iteration protocol of the string, the object that the method returns to us is a StringIterator object that we can loop through using a `for of` loop. If we want to extract the values without looping through the object we can use the next() method available to all iterators.

```javascript
console.log(myIter.next())
// { value: 's', done: false }
```

The next() function returns to us an object with two properties;

* value - which represents the current item in the iterable the pointer is currently at;
* done - this value is a boolean that returns false as long as the item is not on the last item on the list.

## Creating an Iterator from an array

 We can also create an iterator from the values of an array, since arrays are iterables and they have an iteration protocol, building on our example with strings;

```javascript
const arr = [1, 'three', 'supes', 4]

const myIter = arr[Symbol.iterator]()

console.log(myIter) // ArrayIterator{}

console.log(myIter.next())
//{ value: 1, done: false }

// looping through it using a for of loop
for (v of myIter) {
  console.log(v)
}
// 1, three, supes, 4
```

We can also create an iterator from an array using the array.values() method, this method returns to us the ArrayIterator object that contain the items in the array;

```javascript
const arr = [1, 'three', 'supes', 4]

const myIter = arr.values()

console.log(myIter) // ArrayIterator{}

console.log(myIter.next())

//{ value: 1, done: false }
```

## Creating an iterator from a map object

Remember what we said about maps in the last article? Maps have two methods map.prototype.values() and map.proptotype.keys(). These methods both return iterators when they are called on a map object, the earlier will return an iterator of the values for each key in the map object while the later will return an array of the keys on the map object.

```javascript
const map = new Map()

map.set('superman', { name: 'superman', alias: 'clark kent'})
map.set('batman', { name: 'batman', alias: 'bruce wayne'})

// from the values of the object
const myIter = map.values()

console.log(myIter.next())
// { value: { name: 'superman', alias: 'clark kent' }, done: false }

// from the keys of the object
const iterable = map.keys()

console.log(iterable.next())
// { value: 'superman', done: false }


// using [Symbol.iterator]()
const myIterator = map[Symbol.iterator]()

console.log(myIterator) // MapIterator
console.log(myIterator.next()) 
// { value: [ 'superman', { name: 'superman', alias: 'clark kent' } ],
//  done: false }
```

## Creating an Iterator from a Set

We can create an array from a set by calling the set.prototype.values() method on the set, it returns an iterator that contains the value we stored inside the set; 

```javascript
const set = new Set()

set.add({ name: 'superman', alias: 'clark kent'})
set.add({ name: 'batman', alias: 'bruce wayne'})

// using set.prototype.values
const arr = set.values()

// using [Symbol.iterator]()
const iterable = set[Symbol.iterator]()

console.log(arr.next())
// { value: { name: 'superman', alias: 'clark kent' }, done: false }
console.log(iterable.next())
// { value: { name: 'superman', alias: 'clark kent' }, done: false }
```



## Creating an custom Iterator

We have been creating iterators based on built in iterables in JavaScript, we can create our own custom iterator by using attaching a  function to the object that returns a next method. We have to define a custom behavior for the iterator inside the next() method.

```javascript
// creating a custom iterator
class Iter {
    constructor(iter){
        this.iter = iter
    }
  
    iterator () {
        let index = 0 // this is our pointer it will start from 0
        const length = this.iter.length;
        const list = this.iter // we wont have access to this.iter inside next()
        return {
            next () {
                if (index < length) {
                    return { value: list[index++], done: false}
                }
                return { value: undefined, done: true}
            }
        }
    }
}


const myIter = new Iter([1, 2, 3, 4, 5])
const iter = myIter.iterator()

console.log(iter) // { next: function}
console.log(iter.next()) { value: 1, done: false }
```



## Generators

Generators simplify the creation of iterators, we can define an iteration sequence by writing one function that executes in a continuous state. We write generator functions just the way we write other functions, however for a generator function, we add an asterisk `(*) `immediately after the function keyword. When we call a generator function, the code is not immediately executed, instead it returns an iterator that we can consume by calling the next() method. Inside a generator function we use the **yield** keyword to pause the execution of the generator functions, and that's the cool thing about generators. We can pause their execution with **yield** keyword, the **yield** keyword also returns the value immediately after it. We can have multiple yield statements in a generator.

```javascript
function* makeIterator(items) {
    // define custom iteration sequence
    let index = 0;
    for(index; index < items.length; index++){
        yield items[index] // yield keyword also returns multiple values
    }
    return index
}

const myGen = makeIterator([1, 2, 3, 4])

console.log(myGen.next()) // { value: 1, done: false }

// we can iterate over it using a for of loop
for (i of myGen){
    console.log(i)
}
// 1, 2, 3, 4
```



## Creating Iterables

We have looked at the different types of built in iterables and how to create iterators from them. We have also seen how we can create our own iterators manually and also using generator functions. We can create our own iterable by attaching a `*[Symbol.iterator]()` function to the object we want to make an iterable. 

```javascript
const Iterable = {
    *[Symbol.iterator] () {
        yield 1;
        yield 2;
        yield 3
    }
}

for (i of Iterable){
    console.log(i)
}
// 1, 3, 3
```

That is it for this. I do hope that you find this useful.