nzakas / understandinges6

Content for the ebook "Understanding ECMAScript 6"
5.45k stars 796 forks source link

for..of only works with iterables (instead of iterators) #119

Closed sprengr closed 9 years ago

sprengr commented 9 years ago

First of all: thanks for this amazing book, it's the most complete overview I've found so far with very helpful examples. The code provided in your for..of example didn't work for me, I get following error message in FF (which by now should be able to understand all the needed concepts):

TypeError: iterator[Symbol.iterator] is not a function

MDM describes it like this:

The iteration protocols consists of the "iterable" protocol and the "iterator" protocol. [...] In order to be iterable, an object must implement the @@iterator method, meaning that the object (or one of the objects up its prototype chain) must have a property with a Symbol.iterator key. [...] An object is an iterator when it implements a next() method.

So I would change the example to either use two objects (an iterable providing an iterator) or one object which is both and returns itself.

using two objects would look like this:

let iterator = createIterator([1, 2, 3]);
let iterable = {
    [Symbol.iterator]() {
        return createIterator([1, 2, 3])
    }
};

for (let i of iterable) {
    console.log(i);
}

and using one would change the previous defined createIterator function to something like this:

function createIterator(items) {

    var i = 0;

    return {
        //iterable
        [Symbol.iterator]() {  
            return this; 
        },
        //iterator
        next: function() {

            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined;

            return {
                done: done,
                value: value
            };

        }
    };
}

In my opinion the first one is easier to understand, because it doesn't "pollute" the first example where the reader gets introduced to the iterator concept.

getify commented 9 years ago

Yeah, the confusion is that a for..of loop doesn't want an iterator, it wants an iterable. This seems nuanced, but it's important to keep them separate. This great post by @domenic does a really good job of defining them in a clear way.

Quoting:

An iterator is an object with a next method that returns { done, value } tuples.

An iterable is an object which has an internal method, written in the current ES6 draft specs as obj[@@iterator](), that returns an iterator.

If you want to pass an iterator in a place where an iterable is expected (like a for..of loop), the typical approach (as OP mentioned) is to make your iterator ALSO an iterable, and this is done by defining a Symbol.iterator function on your iterator that just does return this in it.

For example, when you execute a generator, you get a combo iterable + iterator object back, so that it's suitable to pass directly into a for..of loop. I expect this will be a fairly common pattern to combine them. It makes most sense IMO to assume that, and present the topic and example in that way.

FWIW, here's how I cover the details of Iterables vs. Iterators in my YDKJS: Async & Performance book.

nzakas commented 9 years ago

Thanks, I'll take a look at making corrections when I have some time.