Skip to content

Commit

Permalink
async+perf: ch4, discussion of iterators/iterables
Browse files Browse the repository at this point in the history
  • Loading branch information
getify committed Oct 23, 2014
1 parent 77de5ae commit c3c9bce
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 1 deletion.
200 changes: 199 additions & 1 deletion async & performance/ch4.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,208 @@ But wait!? There's still an extra `next()` compared to the number of `yield` sta

The `return` statement answers the question!

And if there **is no `return`** in your generator -- they are certainly not any more required in generators than in regular functions -- there's always an assumed/implicit `return;` (aka `return undefined;`), which serves the purpose of answering the question *posed* by that final `it.next(7)` call.
And if there **is no `return`** in your generator -- `return` is certainly not any more required in generators than in regular functions -- there's always an assumed/implicit `return;` (aka `return undefined;`), which serves the purpose of default answering the question *posed* by the final `it.next(7)` call.

These questions and answers -- the two-way message passing with `yield` and `next(..)` -- are quite powerful, but it's not obvious at all how these mechanisms is connected to async flow control. We're getting there!

## Generator'ing Values

In the previous section, we alluded to an interesting use-case for generators. This use-case is **not** the main use-case we're concerned with in this chapter, but I'd be remiss if we didn't cover it in brief, especially since it essentially belies the origin of the name: generators.

We're going to take a slight diversion into the topic of *iterators* for a bit, but we'll circle back to how they relate to generators and to the use-case of using a generator to *generate* values.

### Producers and Iterators

Imagine you're *generating* a series of values where each value has a definable relationship to the previous value. To do this, you're going to need a stateful producer that remembers the last value it gave out.

You can implement something like that straightforwardly using a function closure (see the *"Scope & Closures"* title of this series):

```js
var gimmeSomething = (function(){
var last;

return function(){
if (last === undefined) {
return (last = 1);
}

last = (3 * last) + 6;

return last;
};
})();

gimmeSomething(); // 1
gimmeSomething(); // 9
gimmeSomething(); // 33
gimmeSomething(); // 105
```

Generating an arbitrary number series isn't a terribly realistic example. But what if you were generating records from a data source? You could imagine much the same code.

In fact, this task is a very common design pattern, usually solved by iterators. An *iterator* is a well-defined interface for stepping through a series of values from a producer. The JS interface for iterators, as it is in most languages, is to call `next()` each time you want the next value from the producer.

We could implement the standard iterator interface for our number series producer above:

```js
var something = (function(){
var last;

return {
// needed for `for..of` loops
[Symbol.iterator]: function(){ return this; },

// standard iterator interface method
next: function(){
if (last === undefined) {
return { done:false, value:(last = 1) };
}

last = (3 * last) + 6;

return { done:false, value:last };
}
};
})();

something.next().value; // 1
something.next().value; // 9
something.next().value; // 33
something.next().value; // 105
```

**Note:** We'll explain the purpose of the `[Symbol.iterator]: ..` part of this code snippet in just a moment. But syntactically, two ES6 features are at play. First, the `[ .. ]` syntax is called a *computed property name* (see the *"this * Object Prototypes"* title of this series). It's a way in object literal definition to specify an expression and use the result of that expression as the name for the property. Next, `Symbol.iterator` refers to one of ES6's predefined symbol values (which will be covered in a later book in this series).

The `next()` call returns an object with two properties: `done` is a boolean signaling the iteration is complete status; `value` holds the iteration value.

ES6 also adds the `for..of` loop, which means that a standard iterator can automatically be consumed with native loop support:

```js
for (var v of something) {
console.log( v );

// don't let the loop run forever!
if (v > 500) {
break;
}
}
// 1 9 33 105 321 969
```

**Note:** Because our `something` iterator always returns `done:false`, this `for..of` loop would run forever, which is why we put the `break` conditional in. It's totally OK for iterators to be never-ending, but there are also plenty of cases where the iterator will run over a finite set of values and eventually return a `done:true`.

The `for..of` loop automatically calls `next()` for each iteration -- note that it doesn't pass any values in -- and it will automatically terminate on receiving a `done:true`. Of course, you could manually loop over iterators, calling `next()` and checking for the `done:true` condition to know when to stop.

```js
for (
var ret;
(!ret || !ret.done) && (ret = something.next());
) {
console.log( ret.value );

// don't let the loop run forever!
if (ret.value > 500) {
break;
}
}
// 1 9 33 105 321 969
```

In addition to making your own *iterators*, many built-in data structures in JS (as of ES6), like `array`s, also have default *iterators*:

```js
var a = [1,3,5,7,9];

for (var v of a) {
console.log( v );
}
// 1 3 5 7 9
```

The `for..of` loop asks `a` for its *iterator*, and automatically uses it to iterate over `a`'s values.

**Note:** It may seem a strange omission, but regular `object`s intentionally do not come with a default *iterator* the way `array`s do. The reasons go deeper than we will cover here. Of course, you could easily define an *iterator* for an object by making a function that looped over the list of properties returned from `getOwnPropertyNames()`, and pulled out each respective property's value as `next()` was called.

### Iterables

The `something` object in our running example is called an *iterator*, as it has the `next()` method on its interface. But a closely related term is *iterable*, which is an `object` that **contains** an *iterator* that can iterate over the values.

As of ES6, the way to retrieve an *iterator* from an *iterable* is that the *iterable* must have a function on it, with the name being the special ES6 symbol value `Symbol.iterator`. When this function is called, it returns an *iterator*. Though not required, generally each call should return a fresh new *iterator*.

`a` in the previous snippet is an *iterable*. The `for..of` loop automatically calls its `Symbol.iterator` function to construct an *iterator*. But we could of course call the function manually, and use the *iterator* it returns:

```js
var a = [1,3,5,7,9];

var it = a[Symbol.iterator]();

it.next().value; // 1
it.next().value; // 3
it.next().value; // 5
..
```

In the previous code listing that defined `something`, you may have noticed this line:

```js
[Symbol.iterator]: function(){ return this; }
```

That little bit of confusing code is making the `something` value -- the interface of the `something` *iterator* -- also an *iterable*; it's now both an *iterable* and an *iterator*. Then, we pass `something` to the `for..of` loop:

```js
for (var v of something) {
..
}
```

The `for..of` loop expects `something` to be an *iterable*, so it looks for and calls its `Symbol.iterator` function. That function we defined to simply `return this`, so it just gives itself back, and the `for..of` loop is none the wiser.

### Generator Iterator

Let's turn our attention back to generators, in the context of *iterators*. A generator can be treated as a producer of values that we extract one-at-a-time through an *iterator* interface's `next()` calls.

So, a generator itself is not technically an *iterable*, though it's very similar -- when you execute the generator, you get an *iterator* back.

```js
function *foo(){}

var it = foo();
```

We can implement the `something` infinite number series producer from earlier with a generator, like this:

```js
function *something() {
var last = 1;
while (true) {
yield last;
last = (3 * last) + 6;
}
}
```

That's significantly cleaner and simpler, right!? And now we can use this `*something()` generator with a `for..of` loop:

```js
for (var v of something()) {
console.log( v );

// don't let the loop run forever!
if (v > 500) {
break;
}
}
// 1 9 33 105 321 969
```

Pay close attention to `for (var v of something()) ..`. We didn't just reference `something` as a value like in earlier snippets, but instead called the `*something()` generator to get its *iterator* for the `for..of` loop to use.

If you're paying close attention, two questions may arise from this interaction between the generator and the loop:

1. Why couldn't we say `for (var v of something) ..`? Because `something` here is a generator, which is not an *iterable*.
2. The `something()` call produces an *iterator*, but the `for..of` loop wants an *iterable*, right? Yep. The generator *iterator* also has a `Symbol.iterator` function on it, which basically does a `return this` like ours did. In other words, the generator *iterator* is also an *iterable*!

## Summary

Generators are a new ES6 function type which does not run-to-completion like normal functions. Instead, the generator can be paused in mid-completion (entirely preserving its state), and it can be resumed from where it left off.
Expand Down
1 change: 1 addition & 0 deletions async & performance/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* Promise Limitations
* Chapter 4: Generators
* Breaking Run-to-completion
* Generator'ing Values
* // TODO
* Appendix A: // TODO
* Appendix B: Thank You's!
Expand Down

0 comments on commit c3c9bce

Please sign in to comment.