Iterators In ES6

iterators in es6

Iterators are a major new feature in ES6, and are used extensively already. Iterators are used for lazy evaluation, or in cases where describing an infinite sequence is needed. Soon we will be looking at Generators, and it helps to have a grasp of iterators before we do so. In fact, Iterators are kind of a prerequisite for Generators, Promises, Sets, and Maps. Let’s look at the basics of Iterators here, and get up to speed with them.

Introduction to Iterators

ES6 now has what’s known as an iterable protocol. It is a protocol that defines the iterating behavior of JavaScript objects. An iterable object has an iterator method with the key Symbol.iterator that returns an iterator object. Let’s see how.

Here we have a simple array of numbers and this is stored in the variable numbers. It turns out, arrays in ES6 now have a special property which can be accessed with the bracket notation we see here. In fact the property name is a symbol, which is also another new feature in ES6. Symbols are just a way to guarantee a unique value. When we log out the typeof this property, it is a function. Interesting! Let’s go a little further.

So check it out. Since Symbol.iterator is a function, that means we can call it. That is exactly what we do on line 2 right above. We assign this result to the iter variable, which is our iterator. That iterator has some special functions associated with it. In this example we call .next() on the iterator and our result is a special object which has two properties of value and done. We see at this time, value is 100 and done is false. This makes since since we are on the first element of the array, and out iterator has not cycled through all the values. Let’s see multiple calls to the iterator now.

Each call to .next() produces the next value in the array. Oddly, the done property is still set to false even though we are on the third and final value. Hmmm. Let’s make another call to .next().

On the N + 1th time we call the .next() function based on the size of the array, we will then reach a done state of true in the iterator object. At this point the value will be set to undefined. In other words, if you have an array with a size of 3, you would need to call .next() four times in order to exhaust the iterator.

Making Your Own Iterator

Iterators are built into arrays, but we can custom make an iterator if we like. We’ll create an object literal named numberGen and give it a property of [Symbol.iterator] similar to how arrays have that property. Then we have a local variable of nextNum set to 500, and we return a function named next() which returns an iterator object that has our value and done properties. When we run the code and make a couple of calls to .next(), we can see the initial value of 500 and the next value of 501. Each call to .next() will increment by one. Pretty cool!

General Format of Object Iterator

An iterable object has an iterator method with the key Symbol.iterator. This method returns an iterator object, and we can see the general syntax of this concept here.

Using an Iterator with for of

Using the iterator object we just created, we can implement a for of loop to loop through a list of values. We set up a variable of value inside the for of, and on each iteration we will grab the next iterator value. We set up a check and examine if the retrieved iterator value is greater than 505 and if so, we cancel the loop. Running the code gives us a result we expect. This iterator has not logic to reach an exhausted state like the built in iterator on arrays, but it does get the idea across. If we didn’t set up some kind of terminating condition, this iterator would iterate forever, and that’s a long time.

Fixing our Iterator to support exhaustion

Typically, you would not need to manually put a check inside the loop to prevent it from going to infinity when working with iterators. We can add some simple logic to our prior iteration (pun not intended), so that it will automatically finish once the collection is finished. Let’s see how to to that.

Using The Spread Operator With Iterators

In our earlier lesson on rest parameters and spread operators, we say how to use the spread operator to spread out an array of values to pass to a function. The spread operator works off of an iterator. In this example here, numbers is an array, but it could also be an iterable object. So numbers gets iterated over, and the parameters to process will be assigned each value.

Multiple Iterators on the Same Iterable Object

Here we will create a counting object, which simply counts from 0 to 3 using iterators.

Again, notice that the state of the iteration maintains each time. In this example above, we are taking a slightly different approach and making use of Object.assign. What this does is to make a shallow copy of the iterator object every time an iterator is returned from the function. By using this approach, you can have several iterators on the same iterable object which each have their own internal state. If we didn’t use Object.assign, there would just be several references to the same exact iterator object.

More Iterator Discussion

Symbol.iterator is what is known as a well known symbol. The examples we have seen so far have used Symbol.iterator to describe an iterable object. We are taking this approach simply for the sake of understanding how iterators work. Typically, you won’t actually need to use Symbol.iterator in your code. Instead you can create an ArrayIterator by calling the entries() method of an array. In the case of objects ArrayIterator yields an array of [key, value] during each iteration. Strings can also be handled as arrays using the spread operator.

Iterators With Sets and Maps

There is an entries() method available to use with sets and maps. The Map data structure in ES6 lets you use arbitrary values as keys and the Set data structure lets you store unique values of any type, whether primitive values or object references. You can also use the keys and values method on a set or map to create an iterator/iterable of the keys or values. Take a look at this example here to see each of these methods in use.

Sets and Maps are iterable in their own right, so it is not even necessary to create these iterators with the keys, values, or entries method. Since Sets and Maps are iterable themselves, they can be used in for-of loops like you see here.

Iterators In ES6 Summary

Iterators in ES6 are a welcome addition to ES6. You will often find them in the context of Generators, Promises, Maps, and Sets. Here is a summary of our learning about Iterables in ES6.

  • Arrays are iterables, often used with for-of loop
  • Strings are iterables as arrays of 2 to 4 byte characters
  • DOM data structures are iterables
  • Maps and Sets are iterables
  • The for-of loop and the Spread operator are data consumers – they consume iterable data
  • Iterable data structures like arrays, strings, maps, sets, and the dom are data sources
  • The iterable protocol specifies how to connect data consumers with data sources
  • Iterable objects are created according to the iterable protocol. Iterable objects can create iterator objects that facilitate the iteration on their data source, and prepare the result for a data consumer