1

Let's assume two similar implementations of an object with a defined iterator: one iterator using generators, the other using iterables. Both of these two work with Array.from, and both of them can be iterated over. What are the differences in these two approaches, which one is preferred, and why? Is there ever a need for the lesser approach?

class Foo {
  constructor( ...args ) {
    this.f = args;
  }
  [Symbol.iterator]() {
    let c = 0;

    const i = {

      next: () => {
        if ( c < this.f.length ) {
          return {value:this.f[c++], done: false};
        }
        else {
          return {value:undefined,done:true};
        }
      }

    };
    return i;
  }

};

class Bar {
  constructor( ...args ) {
    this.f = args;
  }
  *[Symbol.iterator]() {
    let c = 0;

    if ( c < this.f.length ) {
      yield this.f[c++];
    }
    else {
      return;
    }
  }

};

Here we can test them both to show that they're essentially the same.

var o1 = new Foo([1,2,3]);
for ( let x of o1 ) {
  console.warn(x)
}
console.log(o1, Array.from(o1));

var o2 = new Bar([1,2,3]);
for ( let x of o2 ) {
  console.warn(x)
}
console.log(o2, Array.from(o2));
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • 3
    For me, a generator is just syntactic sugar for creating an iterator. Of course it is is a very powerful syntactic sugar the more complex your code gets, since you don't have to maintain iterator state yourself. Personally I'd always prefer a generator because of less boilerplate. – Felix Kling Jul 14 '17 at 01:59

1 Answers1

6

two similar implementations of an object with a defined iterator: one iterator using generators, the other using iterables.

Let's correct the terminology first: You have defined two (constructors for) objects that are Iterables. They are both iterable in the sense that they have have Symbol.iterator method that returns an iterator - an object with a next method. One of these methods is implemented by literally returning an object, the other is implemented using generator syntax.

We can test them both to show that they're essentially the same.

Uh, no, you've made an essential mistake: you've used rest parameters in your constructors, so both of your objects ended up with an array of one array as their f value.

If you used either var o = new FooBar(1, 2, 3) or constructor(args) {, the property would be what you expected and the examples would show that they absolutely don't do the same thing.

So let's fix your code:

class Test {
  constructor(arr) {
    this.f = arr;
  }
}
class Foo extends Test {
  [Symbol.iterator]() {
    let c = 0;
    return {
      next: () => {
        if ( c < this.f.length ) {
          return {value: this.f[c++], done: false};
        } else {
          return {value: undefined, done: true};
        }
      }
    };
  }
}
class Bar extends Test {
  *[Symbol.iterator]() {
    let c = 0;
    while (c < this.f.length) // written a lot nicer using a `for` loop
      yield this.f[c++];
    // return undefined; // we really should omit that
  }
}

for (let test of [Foo, Bar]) {
  console.log(test.name);
  const o = new test([1,2,3]);
  for (const x of o)
    console.log(x)
  console.log(Array.from(o));
}

This now does what you actually wanted.

What are the differences in these two approaches?

I hope it's clear from the above code: generator functions are much simpler.

Which one is preferred, and why?

Make a guess :-) Syntactic sugar improves readability and simplifies complex behaviours through abstraction.

Is there ever a need for the lesser approach?

I can't imagine any standard use case. Of course generator syntax is a feature that needs to be supported by the engine, but so is the complete iteration protocol. Maybe there are some edge cases where a hand-crafted micro-optimised iterator object is faster/cheaper/lighter than a generator, e.g. for constant infinite iterators, but I doubt it.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I've clarified the title. Could you answer further whether there is ever a better use of objects with iterators, over generators? – Evan Carroll Jul 14 '17 at 20:08
  • 1
    I can't imagine any real-world use case. Of course generators are out when their syntax is not supported, but in that case all the other ES6 features wouldn't either. And maybe there are some edge cases where a hand-crafted iterator object is faster/cheaper/lighter/better than a generator, e.g. for constant infinite iterators. – Bergi Jul 14 '17 at 20:15
  • if you update the answer with that, I'll mark it as chosen. That covers everything I wanted to learn. – Evan Carroll Jul 14 '17 at 20:21