Iterators and Generators in JavaScript gives us a more expressive write expressive lazy code. Lazily executed code can give us a bit of a performance boost because computations can be delayed until they are needed. This allows us to create some very interesting functions that can generate an infinite amount of output.

Iterable / Iterator

Iterator is a pattern for Iterating over a collection. It is a widely used pattern in many Object Oriented Programming languages - implemented as Iterable in Java and IEnumerable in .NET.

The design is simple, an Iterable has a method next() that returns a Tuple containing the properties identifying whether the Iterator is done and output value.

We can illustrate it’s design with two simple interfaces in a static language with Generic support.

1
2
3
4
5
6
7
8
interface Iterator<T> {
    T next()
    bool done()
}

interface Iterable<T> {
    Iterator<T> iterator()
}

Or in a dynamic language such as JavaScript:

1
2
3
4
5
6
7
8
interface Iterator {
    any next()
    bool done()
}

interface Iterable {
    Iterator getIterator()
}

So what’s exactly new? well, the Iterator is now exposed and we are also now able to set Iterator on our own defined Objects allowing us to create Iterable Objects.

It’s something that is used internally by the loop expression for .. of ..

What’s the difference between “for of” and “for in”?

for .. of .. iterates over an object using the Iterator, where as for .. in .. iterates all of the properties of an Object, including it’s inherited prototype properties.

How do we make our object Iterable?

Now that we have access to the internals of what makes an Object Iterable, we can now define our own Iterable Object - this is done by returning our Iterator in a special method named [Symbol.iterable].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let iterableObject = (() => {
  
  let index = 0
  
  let arr = [1,2,3,4,5]
  
  // an Iterator is an object with the
  // function next, returning a tuple { value, done }
  let iterator = {
    next: () => 
    	(index < arr.length) 
      	? { value: arr[index++], done: false}
    		: { done: true }
  }
  
  return {
    // an Iterable is an Object that returns an Iterator
    // this is our equivalent of "getIterator"
    [Symbol.iterator]: () => iterator
  }
})() // IIFE to create a closure

for(let item of iterableObject) {
  console.log(item) // 1, 2, 3, 4, 5
}

Any Object with it’s own [Symbol.iterable] method is Iterable.

Generators

Generators allows us to lazily return values. if you’re coming from C#, Python, PHP, C, C++ or Java, then chances are, then you’re already familiar with the concept as most languages support generators.

In JavaScript, a Generator is a function that returns an Iterator - as we recall, an Iterator is simply a factory that computes for the next result when .next() is called.

Because nothing is evaluated until needed, we can actually create a generator that can yield an infinite amount of output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let createGenerator = (function() {
  let i = 0
  return function* numberGenerator() {
    while(true) {
      yield i++
    }
  }
})()

let myGenerator = createGenerator()
console.log(myGenerator.next()) // 0
console.log(myGenerator.next()) // 1
console.log(myGenerator.next()) // 2
console.log(myGenerator.next()) // 3

Advanced Generators

Here’s a thing many other implementations of generators in other languages don’t support, you can pass an argument to next(). calling next() on the iterator the generator returns gets passed as the value that’s been yield. This allows us to do some interesting things, one of which is being able to go to a certain point of a sequence without necessarily evaluating the previous one’s.

Let’s take a look at an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let createGenerator = (function() {
  
  let i = 0
  let lastYield
  
  return function* numberGenerator() {
    while(true) {
      
      console.log(`i: ${i}`)
      console.log(`lastYield: ${lastYield}`)
      
      if(lastYield != undefined && lastYield != i) {
        console.log(`replacing i with lastYield`)
        console.log(`i: ${i}`)
      	console.log(`lastYield: ${lastYield}`)
        i = lastYield
      }
        
      lastYield = yield i++
    }
  }
})()

let myGenerator = createGenerator()

If you’re using the latest chrome - you should be able to paste this in the console and run it.

Type and execute the following line by line:

1
2
3
4
5
6
7
myGenerator.next() // 0
myGenerator.next() // 1
myGenerator.next() // 2
myGenerator.next(100) // 100
myGenerator.next() // 101
myGenerator.next() // 102
myGenerator.next(1) // 1

You will notice that yield i++ actually gets transformed to something equivalent to yield arguments[0] and this gets assigned to lastYield - the main limitation of this is that you cannot assign anything to lastYield during the first call to next.