20

I would like to set the options[Symbol.iterator] property in order to iterate on the simple objects I create with the for...of statement :

options = {
  male: 'John',
  female: 'Gina',
  rel: 'Love'
};


for(let p of options){
  console.log(`Property ${p}`);
};

But this code gives me the following error:

 array.html:72 Uncaught TypeError: options[Symbol.iterator] is not a function

How I can set the right iterator function on a simple object as above?

Solved

 // define the Iterator for the options object 
 options[Symbol.iterator] = function(){

     // get the properties of the object 
     let properties = Object.keys(this);
     let count = 0;
     // set to true when the loop is done 
     isDone = false;

     // define the next method, need for iterator 
     let next = () => {
        // control on last property reach 
        if(count >= properties.length){
           isDone = true;
        }
        return {done:isDone, value: this[properties[count++]]};
     }

     // return the next method used to iterate 
     return {next};
  };

And I can use the for...of statement on my object now iterable :

 for(let property of options){
   console.log(`Properties -> ${property}`);
 }
Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
cicciosgamino
  • 871
  • 3
  • 10
  • 29
  • 2
    To use a `for...of` loop, the collection would have to have a `[Symbol.iterator]` property. Object literals do not have such a property, only arrays, Sets, maps etc do – adeneo Mar 05 '16 at 21:02
  • You want to use a `Map` for that. – Bergi Mar 05 '16 at 23:00

3 Answers3

32

To use for...of loop you should define an appropriate iterator for your object using [Symbol.iterator] key.

Here is one possible implementation:

let options = {
  male: 'John',
  female: 'Gina',
  rel: 'Love',
  [Symbol.iterator]: function * () {
    for (let key in this) {
      yield [key, this[key]] // yield [key, value] pair
    }
  }
}

Though, in most cases it'll be better to iterate over objects using plain for...in loop instead.

Alternatively you could convert your object to iterable array using Object.keys, Object.values or Object.entries (ES7).

Leonid Beschastny
  • 50,364
  • 10
  • 118
  • 122
  • 1
    You really should just do `[Symbol.iterator]() { return Object.entries(this) }` (apart from the fact that there is no `Object.entries` in ES6) – Bergi Mar 05 '16 at 23:02
  • 2
    @Bergi no, it won't work. You have to return an iterator, not an iterable Object. To re-use iterator from `Object.entries` you should `return Object.entries(this)[Symbol.iterator]()`. – Leonid Beschastny Mar 06 '16 at 08:44
  • Ouch, I totally forgot that `Object.entries` is a ES7 draft and not a part of ES6. – Leonid Beschastny Mar 06 '16 at 08:51
  • Doesn't `entries` already return an iterator? But maybe I'm wrong, or there are multiple drafts. – Bergi Mar 06 '16 at 11:15
  • 2
    @Bergi noop, [`Object.entries`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) returns a plain JS array of `[key, value]` pairs. – Leonid Beschastny Mar 06 '16 at 13:40
  • It should only return `this[key]`. from spec: `for (variable of iterable) {statement}` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of – ThaJay Apr 05 '18 at 15:03
  • 1
    This is the way I implemented it: `for (let value of Object.values(this)) yield value` – ThaJay Apr 06 '18 at 11:11
13

If you dont want to use generator syntax, you can use another way define iterator function.

    var options = {
        male: 'John',
        female: 'Gina',
        rel: 'Love',
        [Symbol.iterator]: function () {
            var self = this;
            var values = Object.keys(this);
            var i = 0;
            return {
                next: function () {
                    return {
                        value: self[values[i++]],
                        done: i >= values.length
                    }
                }
            }
        }
    };

    for (var p of options) {
        console.log(`Property ${p}`);
    }
Rogue
  • 11,105
  • 5
  • 45
  • 71
Dmitriy
  • 3,745
  • 16
  • 24
  • 1
    I like this answer because unlike the generator method, this option allows you to return an object with whatever keys you want (as oppossed to just returning 'value' and 'done') For instance, you could return an object with 'key':values[i++]. – Stephen Agwu Jan 17 '18 at 04:32
6

Plain objects (options in this case) are not iterable in ES6. You need to either define an iterator for your object or do something like:

for(let k of Object.keys(options)) {
  console.log(`Property ${k}, ${options[k]}`);
};
hallucinations
  • 3,424
  • 2
  • 16
  • 23