Introduction to Iterables and Iterators in JavaScript

Vijay Prasanna

JavaScript supports a protocol by which objects such as arrays can be used by control structures such as for…of and the spread operator ... to loop through data sequentially. This is referred to as the iterable and the data structures that support this functionality are called iterables. While JavaScript provides maps, arrays and sets with an iterable property from the get-go, plain objects do not have this by default.

Iterables are data structures which provide a mechanism to allow other data consumers to publicly access its elements in a sequential manner. Imagine a self-packaged data structure that unloads data one-by-one in order when put inside of a for...of loop.

The concept of the iterable protocol can be split into the iterable (the data structure itself) and the iterator (sort of a pointer that moves over the iterable). Let’s consider an array for example, when the array is used in a for...of loop, the iterable property is called which returns an iterator. This iterable property is namespaced as Symbol.iterator and the object that it returns can be used on a common interface that is shared by all looping control structures.

In a way, the Symbol.iterator can be compared to a iterator factory that produces an iterator whenever the data structure is placed in a loop.

JavaScript iterable

As an iterator moves over the data structure and provides the elements sequentially, the object returned by the iterable contains a valueand a done property.

JavaScript iterator

The value indicates the current data value pointed by the iterator and done is a boolean that tells us if the iterator has reached the last element in the data structure.

This {value, done} is consumed by structures such as loops. So how does the iterator method call the next object? Using a next() method that’s defined within the Symbol.iterator() method.

A better definition for the iterator property that I can come with at this point is that it’s an property that knows how to access elements from a collection one by one and also provides a logical rule to stop doing so (eg. if there are no more elements in the array).

Objects and Iterables

JavaScript objects are cool and all, but why don’t they have iterables? Well, some of the reasons could be:

  • One of the key features of objects is that it’s user defined. So slipping in a silent [Symbol.iterator]() into the object would make for a nasty surprise.
  • The above point also means that it can be added manually by the user, considering that all object compositions might not be similar. So having a common iterable property is pretty meaningless.
  • If you want to loop over the top level elements in the object, then use the other guy: a for...in loop.
  • The usage of the Maps object type might be more appropriate.

All the points above except the last one (I hate to admit that I’m too comfy with regular objects to move to maps) are good reasons not to have iterables in objects, but what if your boss wanted your JavaScript objects to have one?

A simple iterable implementation on objects would look like this:

let Reptiles = {
  biomes: {
    water: ["Alligators", "Crocs"],
    land: ["Snakes", "Turtles"]
  },

  [Symbol.iterator]() {
    let reptilesByBiome = Object.values(this.biomes);
    let reptileIndex = 0;
    let biomeIndex = 0;
    return {
      next() {
        if (reptileIndex >= reptilesByBiome[biomeIndex].length) {
          biomeIndex++;
          reptileIndex = 0;
        }

        if (biomeIndex >= reptilesByBiome.length) {
          return { value: undefined, done: true };
        }

        return {
          value: reptilesByBiome[biomeIndex][reptileIndex++],
          done: false
        };
      }
    };
  }
};

// let's now iterate over our new `Reptiles` iterable:
for (let reptile of Reptiles) console.log(reptile);

The output would be:

Alligators
Crocs
Snakes
Turtles

With this example, we see iterators can be implemented within the object. Iterables can be powerful properties for objects that provide ease of use while handling certain situations and help us avoid writing long path names.

Getting at the Iterator

Loops like for...of have a built-in mechanism to consume iterables until the done value evaluates to true. What if you want to consume the iterable on your own though, without a built-in loop? Simple, you get the iterator from the iterable and then call next() on it manually.

Given the same example as above, we could get an iterator from Reptiles by calling its Symbol.iterator like this:

let reptileIterator = Reptiles[Symbol.iterator]();

You can then use the iterator like this:

console.log(reptileIterator.next());
// {value: "Alligators", done: false}
console.log(reptileIterator.next());
// {value: "Crocs", done: false}
console.log(reptileIterator.next());
// {value: "Snakes", done: false}
console.log(reptileIterator.next());
// {value: "Turtles", done: false}
console.log(reptileIterator.next());
// {value: undefined, done: true}

console.log(reptileIterator.next());
// TypeError: Cannot read property 'length' of undefined

As you can see, the iterator has a next() method that returns the next value in the iterable. The value for done only evaluates to true after another next() call once the last value has been returned, so to go over the entire iterable there will always be one more call to next() than there is data in the iterable. Calling next() again after an iterator has reached the end of the itarable will result in a TypeError being thrown.

Wrapping Up

I hope that this introduction was eye opening in understanding a little more about JavaScript’s internals for data structures such as objects. This only scratched the surface and, if you want to learn more, I invite you to read Kyle Simpson’s excellent chapter on Iterables.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#