What are Iterables in JavaScript?

Iterables give the ability for a JS object to be iterated and to be used in a 'for of' loop.

A typical use case for a 'for of' loop is when looping across an array.

const arr = [1,2,3,4,5]
for(let num of arr){
 console.log(num); 
}

However, we can't loop a standard JavaScript object, and if you try to do it, you will get a syntax error.

Therefore we extend a JS Object with the iterable capability to loop over its contents in whichever way we want.

Let's convert a simple object into an iterator

For example, take a look at this range object with two attributes: a start value and an end value, defining a range.

The iterator would start looping from the starting value until it reaches the ending value.

 const range = {
   start:1,
   end: 6 
 }

To convert this range object to an iterable, we need to define a unique attribute inside the object, known as the 'Symbol.iterator'.

The 'Symbol.iterator' attribute is a function that should return an object which has a function called 'next'.

This next function is what makes iterating possible.

The 'next' function should return an object with 2 attributes: ' value' and 'done'.

'value' attribute can be any value that is being iterated (e.g., string, object, etc.)

'done' attribute should be a boolean, which, when true, means that the iteration is over The ‘range’ object implements the ‘Symbol.iterator’ attribute and turns into an iterable.

The current and last variables are used for checking the looping criteria inside the next function. The current variable is the return value of the iterator.

const range = {
  start: 1, 
  end: 6
}

range[Symbol.iterator] = function () {
 return {
   current: this.start, 
   last: this.end,

   next: function () {
      if (this.current <= this.last) {
        return {value: this.current++, done: false} 
      } else {
        return {done: true}
      }
   }

 }
}

for (let num of range){
  console.log(num); 
}

Like how an array is iterated using a 'for of' loop, we can iterate the 'range' object. We can also use the spread operator on the 'range' object, just as we do in an array.

const arr = [1,2,3,4,5,6] 

for (let num of arr) {
 console.log(num); 
}

console.log([...arr]); //prints [1,2,3,4,5,6]

//Here is our 'range' object, it starts from 1 to 6 
for (let num of range) {
  console.log(num); 
}

console.log([...range]); //prints [1,2,3,4,5,6]

We can also implement the ‘Symbol.iterator’ attribute inside the ‘range’ object as well, as opposed to doing it outside the object.

const range = {
  start: 1,
  end: 6,

  [Symbol.iterator]: function () {
    return {
      current: this.start,
      last: this.end,

      next: function () {
        if (this.current <= this.last) {
          return {
            value: this.current++,
            done: false,
          }
        } else {
          return { done: true, value:this.current }
        }
      },

    }
  },

}
console.log([...range]) //prints [1,2,3,4,5,6]

Here is an equivalent alternative of the above code snippet, however this implementation of the iterator is more ‘cleaner’ compared to the above code snippet

const range = {
  start: 1,
  end: 6,

  [Symbol.iterator]() {
    this.current = this.start
    return this
  },

  next() {
    if (this.current <= this.end) {
      return { done: false, value: this.current++ }
    } else {
      return { done: true }
    }
  },

}

console.log([...range]) //prints [1,2,3,4,5,6]

Resources

All the code implementations can be found in the JS Fiddle link below jsfiddle.net/xwmot3kp

References

  1. MDN Web Doc on JS Iterables

  2. MDN Reference on Symbol.iterable