Iterables & Iterators in JavaScript

Fri Dec 01 2023

Iterables are objects that can be iterated by for..of loop and can also return iterators explicitly. The best examples are Array,Map and Set data structures in JavaScript.

Symbol.iterator

Symbol.iterator is a well known system symbol in JavaScript. when any object has a property of Symbol.iterator then that object can be iterated over for..of loop. let's make our iterator to understand how it works.

const range = {
  from: 1,
  to: 10,
};

// We want the for..of to work:
// for(let num of range) ...

Currently, the range object does not have Symbol.iterator property, Therefore if we try using for..of loop it will throw an error. As we discussed to make the range object iterable we need to add the iterable property.

Note :

Remember the Symbol.iterator must return an object having the next method.

const range = {
  from: 1,
  to: 5,
};

range[Symbol.iterator] = function () {
  let start = this.from;
  let end = this.to;
  return {
    next() {
      if (start <= end) return { done: false, value: start++ };
      //^closure applied
      return { done: true, value: undefined };
    },
  };
};

for (let num of range) {
  console.log(num); // 1 2 3 4 5
}

Similarly, you can add the property to an object and make it iterate as you like. If you have noticed string type is iterable with for..of loop, it also has implemented the property and returns characters at each iteration.

Note :

Remember that the next method should return the object having done and value property.

const str = "Hello";
for (let char of str) {
  console.log(char); // H e l l o
}

Explicit iterator

You can also directly access the Symbol.iterator property and call it and it will return an iterator object in which you have next() method.

const itr = range[Symbol.iterator]();
while (true) {
  const { done, val } = itr.next();
  if (done) break;
  console.log(val); // 1 2 3 4 5
}

This is also helpful when you want only a few items from the iterator or the iterator is infinitely long.

Spread Operator (...)

You might be using the spread operator very often but if you keenly observe the spread operator works based on the Symbol.iterator property only. it also iterates under the hood and provides you with spread values. let's recall the spread operator and let's see whether our range object can do it or not.

const arr = [1, 2, 3];
console.log(...arr); // 1, 2, 3

const str = "hello";
console.log(...str); // h, e, l, l, o

// our range obj
console.log(...range); // 1, 2, 3, 4, 5

Yes, it will work because our range object has an iterator property. you can assign it to an array or other data structure or pass it as a spread parameter etc.

Generator function function\*

Regular functions return only one, single value (or nothing). Generators can return (“yield”) multiple values, one after another, on-demand. They work great with iterables, allowing you to create data streams with ease. Generators make your life easy to make iterators. To create a generator, we need a special syntax construct: function\*, the so-called “generator function”.

function* generateRange() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
}

You can also specify Symbol.iterator with a generator function and it not only reduces code but also enhances readability. Let's rewrite our range iterator with the generator function.

const range = {
  from: 1,
  to: 10,
};

range[Symbol.iterator] = function* () {
  let start = this.from;
  let end = this.to;
  while (start <= end) {
    yield start;
    start++;
  }
};

// still works the same for..of loop
for (let num of range) {
  console.log(num); // 1 2 3 4 5
}

// explicit iterator
const generator = range[Symbol.iterator]();
generator.next(); // { "value": 1, "done": false }

Summary

Objects that can be used in for..of are called iterable.

  • Technically, iterables must implement the method named Symbol.iterator.
    • The result of obj[Symbol.iterator]\() is called an iterator. It handles further iteration processes.
    • An iterator must have the method named next() that returns an object with done and next, here done:true denotes the end of the iteration process, otherwise the value is the next value.
  • The Symbol.iterator method is called automatically by for..of, but we also can do it directly.
  • Built-in iterables like strings or arrays, also implement Symbol.iterator.
  • String iterator knows about surrogate pairs.