The iterator pattern enables unified and simple access to the elements stored in data structures. Generators are lightweight coroutines. Think: functions that can be suspended and resumed. Among other things, they help with implementing iterators.
This blog post explains how iterators and generators work in ECMAScript 6. The iterator protocol has recently changed, this post explains the new protocol.
Second, many algorithms (map, filter, etc.) can based on iterators and are decoupled from how data structures store elements.
function createArrayIterator(arr) { let index = 0; return { next() { if (index < arr.length) { return { done: false, value: arr[index++] }; } else { return { done: true }; } } } }
class MySpecialTree { ... [Symbol.iterator]() { // (*) ... return theIterator; } }The key of the method starting in line (*) is the symbol Symbol.iterator (not a string).
function createArrayIterator(arr) { let index = 0; return { [Symbol.iterator]() { // protocol: iterable return this; // an iterator! }, next() { // protocol: iterator ... } } }Method iterate returns an iterator, the object itself. Now we can use for-of:
const arr = [ 'red', 'green', 'blue' ]; for(let x of createArrayIterator(arr)) { console.log(x); }In ECMAScript 6, array are iterable and the iteration is over their elements. But there is also a method Array.prototype.entries() for iterating over [index, element] pairs:
let arr = ['foo', 'bar', 'baz']; for (let [index,element] of arr.entries()) { console.log(index + '. ' + element); }The output of this code is:
0. foo 1. bar 2. bazentries() returns an iterable, similar to createArrayIterator(), above.
The following is a generator function (short: generator). It is written like a normal JavaScript function declaration, but with the keyword function* instead of function.
function* generatorFunction() { yield 1; yield 2; }Calling a generator function produces an object for controlling generator execution, a so-called generator object. Such objects are iterators. For example:
> let genObj = generatorFunction(); > genObj.next() { done: false, value: 1 } > genObj.next() { done: false, value: 2 } > genObj.next() { done: true }Generator objects are also iterable – their iterate method returns this.
function traverseTree(tree, visitor) { if (Array.isArray(tree)) { // inner node for(let i=0; i < tree.length; i++) { traverseTree(tree[i], visitor); // (*) recursion } } else { // leaf visitor(tree); } }The function in use:
> const tree = [ 'a', ['b', 'c'], ['d', 'e'] ]; > traverseTree(tree, function (x) { console.log(x) }) a b c d eIf we want to traverse the tree via an iterator and implement that iterator via a generator, we have to figure out how to perform the recursive call in line (*). The problem is that the generator cannot really call itself, because doing so would simply produce a generator object. It can’t use another function, either, because yield is only allowed in generator functions. Thus, ECMAScript 6 provides the dedicated operator yield* for this use case:
function* iterTree(tree) { if (Array.isArray(tree)) { // inner node for(let i=0; i < tree.length; i++) { yield* iterTree(tree[i]); // (*) recursion } } else { // leaf yield tree; } }yield* in line (*) yields everything that is yielded by the iterable that is its operand. It is equivalent to iterating over the operand’s elements and yielding them. Since iterTree returns an iterable, it works with for-of:
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ]; for(let x of iterTree(tree)) { console.log(x); }
function* longRunningTask() { // do something yield; // pause // do something yield; // pause ... }This task would be executed via a scheduling function:
scheduler(longRunningTask());The following is a very simple scheduling function.
function scheduler(task) { setTimeout(function () { if (!task.next().done) { scheduler(task); } }, 0); }So far so good. But what if you want the steps of the long-running task to be performed by other (generator) functions? In a non-generator function, that would look as follows.
function longRunningTask() { let result1 = step1(); // yield let result2 = step2(); // yield step3(result1, result2); } function step1() { ... return result1; } function step2() { ... return result2; } function step3(param1, param2) { ... }If we are to implement the above as a generator function, we have a problem: We need step1 and step2 to return values to their invoker. But until now, we have not seen how generators would be able to do that. If a generator function is called recursively via yield*, it can yield values, but those values don’t reach the location where yield* is used. Therefore, generators can return values in addition to yielding them. A returned value is passed on to yield*:
function* longRunningTask() { let result1 = yield* step1(); yield; let result2 = yield* step2(); yield; yield* step3(result1, result2); } function* step1() { ... return result1; } function* step2() { ... return result2; } function* step3(param1, param2) { ... }Note that the returned value comes after everything has been yielded. This explains why iterators can optionally attach a value to the “end of iteration” marker: it is the value that is returned by generators and returned to yield*.
Node 0.11.13 ships with generators. Alea iacta estNode.js gets generators from V8. Thus, Chrome will support them, soon, too.
Firefox has had iterators and generators for a long time, but they still work slightly differently from the ECMAScript 6 standard.
Additionally, you can keep an eye on the ECMAScript 6 compatibility table to find out how much various engines support.