Iterators in JavaScript

E.Y.
4 min readNov 27, 2020

--

Photo by Krystal Ng on Unsplash

Not long since I started to learn Python, I learnt about iterators and generators. I always heard about in JavaScript as well, in that similar concepts were introduced in ES6. But I never took a close look at it. However, as I spent more time on Python, I found it important to figure out the counterpart concepts in JavaScript (the interesting part when learn another language — it helps you get better in your old one).

So in this blog, we are going to dive deep into Iterators (and Generators in next blog) in JavaScript.

Processing each of the items in a collection is a very common operation. JavaScript provides a number of ways of iterating over a collection, from simple for loops to map() and filter().

Iterators and Generators bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of for...of loops.

source

Iterable

Before jump into Iterators we also need to understand what is an Iterable — essentially, any objects that can be used in for..of are called iterable. But what does for..of take in? An object that implements the iterable protocal.

The iterable protocol allows JavaScript objects to define or customize their iteration behavior. Some built-in types are built-in iterables with a default iteration behavior, such as Array or Map, while other types (like an Object below )are not. Still you get a gut feeling that the object below should be iterated in some certain way.

const iterObj = {
nestedObj: {
bread: [
'Baguette',
'Bagel',
'Toast'
],
milk: [
'Goat',
'Cow',
'Oat'
],
egg: [
'Scrumbled',
'Sunny side up',
'Boiled'
],
},

Often times, we end up writing a customised function to iterate each element in an customised object like above. But this is not easily maintainable. With iterable, however, we can generalise any “iterable” object and iterate through it easily. That’s what ECMA pushed out in ES6.

In order to be an iterable, an object must implement the @@iterator method, meaning that the object must have a property with a @@iterator key which is available via constant Symbol.iterator . Reason that it uses Symbol data type is to ensure the the property name will be unique.

The definition of [Symbol.iterator] uses Computed Property Names and returns a zero-argument function that returns an object, conforming to the iterator protocol.

So what is iterator protocol?

The iterator protocol defines a standard way to traverse a sequence of values.

An object is an iterator when it implements a next() method and returns an object with at least two properties

  • done: boolean

false if the next value in the sequence is still available. The done property is optional in this case.

true if the iterator has reached the end of its sequence. In this case, value optionally specifies the return value of the iterator.

  • value: any

It represents any JavaScript value returned by the iterator. Can be omitted when done is true.

graph representation of iteration protocol

There are many built-in iterables in JavaScript:

Arrays, Strings, Maps, Sets and any thing that can put inside a for-of loop. The popular destructuring operation and spread operator also requires an iterable as object.

To see an example using the iteration protocol, we can look at a random string.

let someString = 'hi';
console.log(typeof someString[Symbol.iterator]); // "function"

String's default iterator returns the string's code points one by one:

let iterator = someString[Symbol.iterator]();
console.log(iterator + ''); // "[object String Iterator]"

console.log(iterator.next()); // { value: "h", done: false }
console.log(iterator.next()); // { value: "i", done: false }
console.log(iterator.next()); // { value: undefined, done: true }

You can redefine the iteration behavior by supplying our own @@iterator:

// need to construct a String object explicitly to avoid auto-boxing
let someString = new String('hi');
someString[Symbol.iterator] = function () {
return {
next: function () {
return this._first ? {
value: 'bye',
done: (this._first = false)
} : {
done: true
}
},
_first: true
};
};

Notice how redefining @@iterator affects the behavior of built-in constructs that use the iteration protocol:

console.log([...someString]); // ["bye"]
console.log(someString + ''); // "hi"

That’s so much of it today. You may notice that it is quite a hassle to construct an iterator inside an iterable. We need to:

  • implement Symbol.iterator function.
  • implement next function.
  • make sure the return object of next() has required properties value and done .
  • save the state of the iterator to calculate when the last item gets retrieved so done is set to false .

So in next blog we will look at how Generator can save us from all the hassle.

Happy Reading!

--

--

No responses yet