JS Tip of the Day: Create Iterable Objects

Create Iterable Objects
Version: ES2015
Level: Advanced

We’ve seen that you can make iterable objects (generators) using generator functions, but you can also turn any ordinary object into an iterable. To do this, all you need is to have a Symbol.iterator property on the object which is a function that returns an iterator object when called. The returned iterator will be used to generate the iterable values within that object. The easiest way to do this? With another generator function of course!

let accounts = {
    checking: 5,
    savings: 0.25
};
for (let balance of accounts) { // TypeError: not iterable
    console.log(balance);
}
// making accounts an iterable
accounts[Symbol.iterator] = function * () {
    for (let key in this) {
        yield `${key}: $${this[key].toFixed(2)}`;
    }
};
for (let balance of accounts) { // now works
    console.log(balance);
}
/* logs:
checking: $5.00
savings: $0.25
*/

Even though objects are not normally iterable, once the accounts object was given the Symbol.iterator property, it became an iterable and was able to work within the for...of. It’s the existence of a Symbol.iterator property that makes any object an iterable.

Objects like arrays are inherently iterable because they already have this property. In the case of arrays, their iterator method is the same as their values() method.

let array = [1, 2, 3];
console.log(typeof array[Symbol.iterator]); // function
console.log(array[Symbol.iterator] === array.values); // true

The same applies to generator objects created by generator functions. What’s interesting about generator objects is that their Symbol.iterator is simply a function that returns the generator object.

function * makeOne () {
    yield 'There can be only one';
}

let highlander = makeOne();
console.log(highlander[Symbol.iterator]() === highlander); // true

If you ever want to make an object an iterable, or even change the default iterable behavior, all you need to do is to define its Symbol.iterator.

And in case you were wondering, the syntax for specifying a Symbol.iterator generator in an object literal (or class) directly would be as follows, using the accounts example from earlier:

let accounts = {
    checking: 5,
    savings: 0.25
    * [Symbol.iterator] () { // generator function
        for (let key in this) {
            yield `${key}: $${this[key].toFixed(2)}`;
        }
    }
};

More info: