Symbol, Iterator and Generator in JavaScript

This post is 4 years old. (Or older!) Code samples may not work, screenshots may be missing and links could be broken. Although some of the content may be relevant please take it with a pinch of salt.

In this article, we'll take a look at Symbols, Iterators and Generators in JavaScript (ES2015) and we'll also implement an interesting example of using all three technologies.

Symbol

Symbol is a new primitive type in JavaScript, and it guarantees us that, at all times, it will have a unique value. What the value is, we don't care (and we don't know as we can't retrieve it), but we can be sure that it'll be unique.

The ECMAScript standard defines 7 data types, out of which 6 are primitive types in JavaScript: Boolean, Null, Undefined, Number, String, and of course Symbol. The seventh data type is Object.

Symbols are used with object properties. There are two types of Symbols that JavaScript gives us: built-in Symbols - for example for iteration and the language also allows us to create a custom symbol where we can specify custom iteration logic via Symbol.iterator.

Creating a Symbol

Creating a Symbol couldn't be easier:

const s = Symbol();
console.log(s); // Symbol()

Remember that Symbols are unique:

const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false

As mentioned earlier, Symbols are best used with objects. There are certain scenarios when we want to create an object, and we would like to have a property that's "secret" or a property that we don't want people to change or update. Normally these would be added by the key __ (double underscore). This is a convention, nothing more:

const obj = {
name: 'Joe',
age: 22,
__: 'secret property value',
};

Now with Symbols, we can add such properties in a much easier way:

const s = Symbol();
const obj = {
name: 'Joe',
age: 22,
};
obj[s] = 'secret property value';

Using the code above means that when we call Object.getOwnPropertyNames(obj) on our object, we will get an array of ['name', 'age'] only but not x. We have successfully "hidden" the property.

Another method Object.getOwnPropertySymbols(obj) does return the symbol itself: [Symbol()].

Try Symbol

Iterator

An iterator allows us to iterate through a collection. A collection, in this case, can be an array or anything else. JavaScript by default implements the Iterator protocol, but it also caters for the creation of custom iterators.

For a custom iterator to be valid, it must implement the iterable protocol (via Symbol.iterator).

A basic example

Let's take a look at the most basic iterator that allows us to print out the values of an array:

const numbers = [1, 2, 3];
for (const number of numbers) {
console.log(number);
}

The above for...of construct uses the Iterator protocol to get the numbers in the array.

Let's take the previous example a little bit further.

const numbers = [1, 2, 3];
const it = numbers[Symbol.iterator]();
console.log(it.next());

The code above returns a custom object structure: { value: 1 done: false }. It's important to understand this object. It has two properties: value which represents the first value from the array, and a done property which indicates that the iteration has not finished.

Calling it.next() again will return the previously seen object structure, except this time the value property will be set to 2. We can keep on calling it.next() until we receive an object where the done property will be set to true. In that case, the value property will have a value undefined. This indicates that there are no more items to iterate through in the collection.

The for...of construct does the same under the hood - it automatically calls the .next() method to iterate through the collection.

A custom iterator

Let's go ahead and create a custom iterator. To do this, we need to create an object:

const obj = {
numbers,
};

At the moment the obj object has access to the numbers array that we saw earlier. Let's extend our object now:

const obj = {
numbers,
idx: 0,
[Symbol.iterator]() {
const it = {
next: () => {
if (this.idx < this.numbers.length) {
const number = this.numbers[idx];
this.idx++;
return { value: number, done: false };
} else {
return { done: true };
}
},
};
},
};

Using the concise property method, as well as the computed property features in JavaScript ES2015 we added the object method Symbol.iterator which requires us also to add an iterator object, which must implement a next method. What we are doing here is simple: We make sure that our custom iterator has the next method available since that will be required to iterate through any collection.

Because we are creating a custom iterator, we can do whatever we want. Notice that we also have an idx property to keep track of index values from the numbers array.

The code above manually implements what the for...of loop is doing. It grabs the first number from the numbers array, and it increments the idx property. It will keep on doing this until there are elements in the numbers array. While there are elements, it returns the { value: ..., done: false } object. Once there are no more numbers available, it changes the done property to true.

We can test our iterator by using the for...of construct:

for (const number of obj) {
console.log(number);
}

Since we now dictate the functionality of our iterator, we can do cool things. For example, wouldn't it be nice if we would list only the prime numbers?

Let's extend our object by adding the following function to it:

isPrime(number) {
for (let i = 2; i < number; i++) {
if (number % i === 0) {
return false;
}
}
return number !== 1 && number !== 0;
}

A prime number is a whole number that is greater than 1 whose only factors are 1 and itself - in other words, it cannot be "created" by multiplying other whole numbers.

Now that we have a function to handle prime numbers we can also extend our implementation of next():

next: () => {
if (this.idx < this.numbers.length) {
const number = this.numbers[this.idx];
if (!this.isPrime(number)) {
this.idx++;
return this[Symbol.iterator]().next();
} else {
const number = this.numbers[this.idx];
this.idx++;
return { value: number, done: false };
}
} else {
return { done: true };
}
};

With return this[Symbol.iterator]().next(); we can skip a value if it's not a prime number, otherwise we return the usual object with the value and done properties.

Running the following code will only return the prime numbers from our array:

// update the numbers array to have more values:
const numbers = [...Array(50 + 1).keys()];

for (const prime of obj) {
console.log(prime);
}

Alternatively, we can also use the spread syntax because that also uses the Iterator protocol:

const primes = [...obj];
console.log(primes);

Try the iterator

Generator

A generator is a particular function in JavaScript that's denoted by the * character. The function itself returns a generator object, and it can pause and resume. With generator functions, the JavaScript language also gets the yield keyword which is capable of pausing the executing of the function.

How does a generator function enter our discussion? A generator function's body is only called when it is going through an iterator. Remember the following: an iterator's next() method invokes the yield keyword in a generator function.

We can easily create a generator function to keep on generating an ID for us:

function* generateID() {
let number = 0;
while (number < number + 1) {
yield number++;
}
}

const it = generateID();
it.next(); // { value: 0, done: false }
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }

Adding a for...of construct would now create a loop that would keep on generating ID numbers forever - be cautious and don't run into this mistake.

Thinking a bit back at what we developed when discussing the Iterators, watchful readers may already know what's coming. If, before we could create our Iterator, why can't we change that code and add a generator function to make the code terser? We can do that!

const numbers = [...Array(50 + 1).keys()];

const obj = {
numbers,
isPrime(number) {
for (let i = 2; i < number; i++) {
if (number % i === 0) {
return false;
}
}
return number !== 1 && number !== 0;
},
*[Symbol.iterator]() {
for (let i = 0; i < this.numbers.length; i++) {
const number = this.numbers[i];
if (this.isPrime(number)) {
yield number;
}
}
},
};

const primes = [...obj];
console.log(primes);

Notice that the [Symbol.iterator]() is now added as a generator function. Because it is added as a generator function, we can call yield to return only the prime numbers.

Try generator

Conclusion

This article intended to walk you through 3 new concepts added to JavaScript as part of ES2015 - Symbols, Iterators and Generators. Individually these features may not be that useful, however combining them together reveals their real power and can make JavaScript a more interesting language.