Blog

Iterables, Iterators and Generator functions in ES2015

17 Aug, 2015
Xebia Background Header Wave

ES2015 adds a lot of new features to javascript that make a number of powerful constructs, present in other languages for years, available in the browser (well as soon as support for those features is rolled out of course, but in the meantime we can use these features by using a transpiler such as Babeljs or Traceur).
Some of the more complicated additions are the iterator and iterable protocols and generator functions. In this post I’ll explain what they are and what you can use them for.

The Iterable and Iterator protocols

These protocols are analogues to, for example, the Java Interfaces and define the contract that an object must adhere to in order to be considered an iterable or an iterator. So instead of new language features they leverage existing constructs by agreeing upon a convention (as javascript does not have a concept like an interface in other typed languages). Let’s have a closer look at these protocols and see how they interact with each other.

The Iterable protocol

This protocol specifies that for an object object to be considered iterable (and usable in, for example, a `for … of` loop) it has to define a function with the special key “Symbol.iterator” that returns an object that adheres to the iterator protocol. That is basically the only requirement. For example say you have a datastructure you want to iterate over, in ES2015 you would do that as follows:
[javascript]
class DataStructure {
constructor(data) {
this.data = data;
}
[Symbol.iterator]() {
let current = 0
let data = this.data.slice();
return {
next: function () {
return {
value: data[current++],
done: current > data.length
};
}
}
}
}
let ds = new DataStructure([‘hello’, ‘world’]);
console.log([…ds]) // [“hello”,”world”]
[/javascript]
The big advantage of using the iterable protocol over using another construct like `for … in` is that you have more clearly defined iteration semantic (for example: you do not need explicit hasOwnProperty checking when iterating over an array to filter out properties on the array object but not in the array). Another advantage is the when using generator functions you can benefit of lazy evaluation (more on generator functions later).

The iterator protocol

As mentioned before, the only requirement for the iterable protocol is for the object to define a function that returns an iterator. But what defines an iterator?
In order for an object to be considered an iterator it must provided a method named `next` that returns an object with 2 properties:
* `value`: The actual value of the iterable that is being iterated. This is only valid when done is `false`
* `done`: `false` when `value` is an actual value, `true` when the iterator did not produce a new value
Note that when you provided a `value` you can leave out the `done` property and when the `done` property is `true` you can leave out the value property.
The object returned by the function bound to the DataStructure’s `Symbol.iterator` property in the previous example does this by returning the entry from the array as the value property and returning `done: false` while there are still entries in the data array.
So by simply implementing both these protocols you can turn any `Class` (or `object` for that matter) into an object you can iterate over.
A number of built-ins in ES2015 already implement these protocols so you can experiment with the protocol right away. You can already iterate over Strings, Arrays, TypedArrays, Maps and Sets.

Generator functions

As shown in the earlier example implementing the iterable and iterator protocols manually can be quite a hassle and is error-prone. That is why a language feature was added to ES2015: generator functions. A generator combines both an iterable and an iterator in a single function definition. A generator function is declared by adding an asterisk (`*`) to the function name and using yield to return values. Big advantage of using this method is that your generator function will return an iterator that, when its `next()` method is invoked will run up to the first yield statement it encounters and will suspend execution until `next()` is called again (after which it will resume and run until the next yield statement). This allows us to write an iteration that is evaluated lazily instead of all at once.
The following example re-implements the iterable and iterator using a generator function producing the same result, but with a more concise syntax.
[javascript]
class DataStructure {
constructor(data) {
this.data = data;
}
*[Symbol.iterator] () {
let data = this.data.slice();
for (let entry of data) {
yield entry;
}
}
}
let ds = new DataStructure([‘hello’, ‘world’]);
console.log([…ds]) // [“hello”,”world”]
[/javascript]

More complex usages of generators

As mentioned earlier, generator functions allow for lazy evaluation of (possibly) infinite iterations allowing to use constructs known from more functional languages such as taking a limited subset from an infinte sequence:
[javascript]
function* generator() {
let i = 0;
while (true) {
yield i++;
}
}
function* take(number, gen) {
let current = 0;
for (let result of gen) {
yield result;
if (current++ >= number) {
break;
}
}
}
console.log([…take(10, generator())]) // [0,1,2,3,4,5,6,7,8,9]
console.log([…take(10, [1,2,3])]) // [1,2,3]
[/javascript]
Delegating generators
Within a generator it is possible to delegate to a second generator making it possible to create recursive iteration structures. The following example demonstrates a simple generator delegating to a sub generator and returning to the main generator.
[javascript]
function* generator() {
yield 1;
yield* subGenerator()
yield 4;
}
function* subGenerator() {
yield 2;
yield 3;
}
console.log([…generator()]) // [1,2,3,4]
[/javascript]

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts