Sammy I.
Sammy I.

Reputation: 2735

Can I make an iterator with a simple function? (No generator or Symbol.iterator)

I have been trying to make an iterator using a plain function, without a generator or using the Symbol.iterator protocol for academic purposes. For that, I have made a function that returns an object with a next parameter, but trying to run it as the iterable argument of an for...of loop yields unwanted results.

Here is my code so far, which I copied from the Iterators and Generators page on MDN:

function iterateThis(arr){
    let i = 0;
    return {
        next: function() {
            return i < arr.length ?
                {value: arr[i++], done: false} :
                {done: true};
        }
     };
}

If I try to run it like so:

const iterable = iterateThis([1,2,3,4,5]);
for(item in iterable){
    console.log(item);
}

On the console, I just get a single result: next.

Am I doing something wrong in the creation of the function iterateThis? Or is for...of only designed to work with generators and the Symbol.iterator property?

Executed on Node v8.11.1

Upvotes: 2

Views: 554

Answers (2)

trincot
trincot

Reputation: 350996

Your function correctly returns an iterator, but there are two issues in the rest of your code:

  • Iterators are not necessarily iterable. In your case iterateThis returns an iterator, but not an iterable. This also means name iterator for your variable is misleading.
  • You've used a for..in syntax (which iterates properties), while you would need for..of (if the object were iterable).

Since ECMAScript 2025 you can make an iterable from any iterator, just by calling Iterator.from, demonstrated in this corrected script:

function iterateThis(arr){
    let i = 0;
    return {
        next() {
            return i < arr.length ?
                {value: arr[i++], done: false} :
                {done: true};
        }
    };
}

const iterable = iterateThis([1,2,3,4,5]);
for (const item of Iterator.from(iterable)) {
    console.log(item);
}

Upvotes: 0

CRice
CRice

Reputation: 32226

The problem is that your iterateThis function returns an iterator but the for/of construct expects a iterable.

Okay, wait, whats the difference?

From MDN's page on iteration protocols:

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 @@iterator key which is available via constant Symbol.iterator:

On the other hand:

An object is an iterator when it implements a next() method with the following semantics: Ommited due to length, TL;DR: The next method returns an object of the form: {value: T, done: boolean}

They are related in that calling the @@iterator method of an iterable returns an iterator.

The for/of loop always expects an iterable, so if you want to use for/of, you have to use @@iterator/Symbol.iterator. There's just no way around it as far as I know. But your snippet can be easily modified to use it by just creating an object that returns your iterator when it's Symbol.iterator method is called:

function iterateThis(arr){
    let i = 0;
    return {
        next: function() {
            return i < arr.length ?
                {value: arr[i++], done: false} :
                {done: true};
        }
     };
}

function makeIterableFromIterator(iterator) {
  return {
    [Symbol.iterator]: function() {
      return iterator;
    }
  }
}

const iterator = iterateThis([1, 2, 3, 4, 5]);
const iterable = makeIterableFromIterator(iterator);

for (item of iterable) {
  console.log(item);
}

Upvotes: 5

Related Questions