Making Iterators¶
To make an object iterable it must have a method named Symbol.iterator
.
This method must return an iterator (an object with the method next
).
next()
needs to return something in the format {done: Boolean, value: any}
Let's have a go with making an iterable that returns Fibonacci numbers - a programming classic.
function fibIter(from, to) {
return {
from,
to,
[Symbol.iterator]() {
return {
previous: 0,
current: this.from,
last: this.to,
next() {
value = this.current+this.previous
;[this.previous, this.current] = [this.current, value]
if (this.current <= this.last) {
return { done: false, value }
} else {
return { done: true }
}
}
}
}
}
}
for (let num of fibIter(1, 20)) {
console.log(num)
}
The code above stores state values in the object as we loop through and returns numbers in the range.
Having the Symbol.iterator
function create and return it's own object instead of updating and returning this
(the object contaning it) means that two or more for
loops can use the iterator at the same time. Otherwise the loops would share the same iteration state and madness would ensue.
The code below demonstrates such a structure:
var myIterator = {
next: function() {
// ...
}
[Symbol.iterator]: function() { return this }
};
If we're doing things this way we need to make sure we create a new iterable object for each time we want to loop over it.
There's more to this which I won't bother explaining here as it's already well described in this post. It's enough for my purposes just to know I need a Symbol.iterator
function returning an object cotaining a next
function.
To infinity and beyond¶
JavaScript has an Inifinity
property that is counted as a numeric value. We can use it to make our loop continue indefinitely.
var i=0
for (let num of fibIter(20, Infinity)) {
console.log(num)
// Use incrementer to count loops and at some point
// stop this running
i++
if(i>5) { break }
}
Obviously these aren't really proper Fibonacci numbers at this starting point, but the logic works the same.
Spread¶
Now that we have a working iterator we can use the spread
syntax on it:
[...fibIter(1,20)]
Array.from¶
We can convert our iterable into an array if we want:
Array.from(fibIter(1,20))
Generators¶
In JavaScript generators are marked with an asterisk at the end of function
:
function* fibGen(from=1, to=20) {
let previous = 0
let current = from
let last = to
while (current < last) {
let value = current + previous
;[previous, current] = [current, value]
yield value
}
return last
}
for (let value of fibGen()) {
console.log(value);
}
Again we can use spread
syntax and Array.from
on this generator:
[...fibGen(1,20)]
Array.from(fibGen(1,20))
Generator Object¶
Generators should probably be assigned to objects instead of calling the function directly - this would allow multiple loops to use the same type of generator without sharing state variables and madness ensuing.
var fibGenObject = function* (from=1, to=20) {
let previous = 0
let current = from
let last = to
while (current < last) {
let value = current + previous
;[previous, current] = [current, value]
yield value
}
return last
}()
The above syntax creates a generator with the iterable functions next
and Symbol.iterator
without us having to explicitly define them:
typeof fibGenObject.next
typeof fibGenObject[Symbol.iterator]
The Symbol.iterator
just returns the generator object itself - so as mentioned above, if two loops where using the same generator object they would fall over each other. But that would be a stupid thing to do, we should make a new generator object each time we want to loop over it.
fibGenObject[Symbol.iterator]() === fibGenObject
This object is an iterable and can have all the same above functions applied to it, including spread
syntax:
[...fibGenObject]
There's more interesting info on iterators / generators here.