Iterables in javascript

Alisha Chhabra
Last Updated: May 13, 2022

Introduction 

In computer science, an object or any data source that can be sequentially iterated is referred to as iterable. Iterable objects are those that can use for-loop and return some value. Consider the array items, which can be iterated over by utilising their indices. You've undoubtedly heard of iterations in computer science, which are related to iterable objects. Iterations are only possible when there is anything on which to iterate. 

Source: meme

In this article, We'll go through each aspect of Iterables in Javascript.

What are the Iterable objects?

An iterable javascript object can return its members one at a time, allowing it to be iterated over in a for-loop. JavaScript includes a protocol that will enable control structures such as for...of and the spread operator... to cycle through data sequentially using objects such as arrays. This is known as the iterable, and the data structures that support it are known as iterables. 

Now, what is the spread operator?

The spread operator is a new addition to the JavaScript ES6 operator set. It takes an iterable (for example, an array) and divides it into individual elements. To produce shallow copies of JS objects, the spread operator is widely employed. Using this operation shortens the code and improves its readability.

Three dots ... represent the spread operator.

To do so, you must understand how Javascript protocols work. 

The Iterator protocol

The iterator protocol defines a standard for producing a sequence of values and a possible return value after all created values.

An object is an iterator when it implements the next() method with the following semantics:

next() method: A function with either zero or one argument returns an object with at least the two properties listed below.

  • value (the next value)
  • done (true or false)
value
Any value returned by the iterator.
(Can be omitted if done is true)

 

done
Has the value false if the iterator has more values to be produced.
Has the value true if the iterator has no other values to be produced.

 

For example:-

// Create a function rangeIterator, provide the default values for its parameters and iterate over the range 
function rangeIterator(start = 0, end = Infinity, step = 1) {
    // Assign the start to nextIndex
    let nextIndex = start;;
    // rangeIterator object
    const rangeIterator = {
        next: function() {
            let result;
            // To check if next Index is less or equals to the end
            if (nextIndex <= end) {
                // If the condition is satisfied, update the result
                result = {
                    value: nextIndex,
                    done: false
                }
                // Increment the next Index
                nextIndex += step;
                // return the result
                return result;
            }
            // If not return undefined in the value and true for done
            return {
                value: undefined,
                done: true
            }
        }
    };
    // return the object
    return rangeIterator;
}
// calling statement 
const obj = rangeIterator(1, 10, 2);

// calls to the next() to get the values and done statement
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());
console.log(obj.next());

 

Output

{ value: 1, done: false }
{ value: 3, done: false }
{ value: 5, done: false }
{ value: 7, done: false }
{ value: 9, done: false }
{ value: undefined, done: true }

 

At this time, the best explanation for the iterator property is that it is a property that understands how to retrieve elements from a collection one by one and offers a logical rule to cease doing so (e.g., if there are no more elements in the array).

Many people are misled into thinking that if an object is an iterator, it must also be iterable, which is incorrect. We won't obtain the same result if we use the for..of or spread operator on the object obj in the preceding example.

const obj = rangeIterator(1, 10, 2);
for (let i of obj){
    console.log(obj.next());
}

 

Output

for (let i of obj){
              ^
TypeError: obj is not iterable

 

Let’s discuss it below with the help of Iterable protocol:-

The Iterable protocol

The iterable protocol enables JavaScript objects to define or customize iteration behavior, such as looping values in a for...of construct. Some built-in types, such as Array or Map, are built-in iterables with default iteration behavior, although others (such as Object) are not.

The iterable protocol notion is divided into two parts: the iterable (the data structure itself) and the iterator (sort of a pointer that moves over the iterable).

For example:-

const arr = [1,2,3,4];
// The right side of the for..of must be an iterable
for (let val of arr){
    // arr is an iterable
    console.log(val);
}

 

Output

1
2
3
4

 

Let’s do this for the non-iterable object:-

const obj = {
    'fname':'Coding',
    'lname':'Ninjas'
}
for (let val of obj){
    // obj is a non-iterable object
    console.log(val);
}

 

Output:-

for (let val of obj){
                ^

TypeError: obj is not iterable
    at Object.<anonymous> (/tmp/cL1QJ0QeF1.js:9:17)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

 

Now, the question is, why are the objects not iterable in javascript?

The JavaScript exception "is not iterable" occurs when the value supplied as the right-hand side of for...of is not an iterable object.

Objects in JavaScript are not iterable unless they implement the iterable protocol. As a result, you cannot use for...of to iterate through an object's attributes.

To iterate through an object's properties or entries, you must use Object.keys or Object.entries.

const obj = {
    'fname':'Coding',
    'lname':'Ninjas'
}
// Iterate over the property names:
for(let key of Object.keys(obj)){
    let value = obj[key];
    console.log(key, value);
}

 

Output

fname Coding
lname Ninjas

 

An object must implement the @@iterator method to be iterable, which means that the object (or one of the objects in its prototype chain) must have a property with a @@iterator key accessible through constant [Symbol.iterator], it further has the next() method for every object. In some cases, if the object is an iterator, i.e., it implements the next() method, the @@iterator method will not be considered iterable. 

Let us understand this by utilizing spread operator and for..of:-

// Maps are iterable as they implement both iterator and iterable protocol
const map = new Map();
map.set('Villa1', 'CodersVilla');
map.set('Villa2', 'NinjasVilla');
map.set('Villa3', 'ProgrammersVilla');

// Right side of the for..of loop must be an iterable
for(let it of map){
    console.log(it);
}

 

Output

[ 'Villa1', 'CodersVilla' ]
[ 'Villa2', 'NinjasVilla' ]
[ 'Villa3', 'ProgrammersVilla' ]

 

// Maps are iterable as they implement both iterator and iterable protocol
const map = new Map();
map.set('Villa1', 'CodersVilla');
map.set('Villa2', 'NinjasVilla');
map.set('Villa3', 'ProgrammersVilla');
// By utilizing the spread operator

console.log(...map);

 

Output

[ 'Villa1', 'CodersVilla' ] [ 'Villa2', 'NinjasVilla' ] [ 'Villa3', 'ProgrammersVilla' ]

 

As we can see, maps in javascript are iterable since they can be executed using the for..of and spread operators. Let's test it with a non-iterable object.

const schools = {
      name: 'Yorktown',
      name: 'Stratford',
      name: 'Washington & Lee',
      name: 'Wakefield'
}
// using the spread operator with a non-iterable object
console.log(...schools);

 

Output

console.log(...schools);
        ^
TypeError: Found non-callable @@iterator
    at Object.<anonymous> (/tmp/majE7xy8VH.js:9:9)
    at Module._compile (internal/modules/cjs/loader.js:778:30)

 

Consider an array: when it is used in a for...of loop, the iterable property is used, which returns an iterator. This iterable property is namespaced as Symbol.iterator, and the object returned by it can be utilised on a common interface shared by all looping control structures.

In some ways, the Symbol.iterator is analogous to an iterator factory, which generates an iterator whenever the data structure is placed in a loop.

Let’s discuss it:-

Symbol.iterator

Symbol.iterator allows developers to reconfigure navigation patterns without copying or altering the object's order in place.

These patterns can then be defined once and used wherever by storing them in a function, class, or module. That is, you can loop through an Array in a modular way forwards, backward, randomly, or eternally.

The Symbol.iterator protocol itself is pretty straightforward. It includes the following:

Iterable: a function-containing object with the key Symbol.iterator.

Iterator: the function mentioned above that is utilised to acquire the values to be iterated.

For example:-

The example below defines an array, marks, then obtains an iterator object. The [Symbol.iterator]() function can be used to get an iterator object. The iterator's next() function returns an object with the attributes 'value' and 'done'. 'done' is a Boolean function that returns true after reading all of the items in the collection.

let marks = [50,100,39]
  let it = marks[Symbol.iterator]();
  console.log(it.next())
  console.log(it.next())
  console.log(it.next())
  console.log(it.next())

 

Output

{ value: 50, done: false }
{ value: 100, done: false }
{ value: 39, done: false }
{ value: undefined, done: true }

 

This informs the Javascript runtime that an Object is iterable. It offers a method for obtaining the next value and determining whether the iteration is complete.

Now that we have gone through each method by which we can determine if the object is iterable or not let us now have a look at some use cases of iterable in javascript:-

 

 

Use cases of Symbol.iterator 

  • Going Backwards
    The two most frequent methods for iterating an array in reverse are indices (from array.length — 1 to 0) and Array.reverse:
let numbers = [1, 2, 3, 4, 5]

for (let i = numbers.length - 1; i >= 0; i--) {
  // print in reverse order
}

for (let num of numbers.reverse()) {
  // print in reverse order
}

console.log(numbers) // [5, 4, 3, 2, 1]

 

OR

    However, both techniques raise questions. When working with indices, it's easy to overlook the -1, resulting in the terrible off-by-one error. 

    However, both techniques raise questions. When working with indices, it's easy to overlook the -1, resulting in the terrible off-by-one error.

    Using Arrays.reverse the array changes it in place, which isn't usually the intent.

    Let us do the same thing with the help of Symbol.iterable:-

 

// Customized iterable for printing the array in reverse order
const reverse = arr => ({
  [Symbol.iterator]() {
      // take the length
    let i = arr.length;
    return {
      next: () => ({
          // started from the last index
        value: arr[--i],
        // until the i becomes 0
        done: i < 0
      })
    }
  }
})
// 
let numbers = [1,3,6,8,9,10]

for (let num of reverse(numbers)) {
  console.log(num)
}

 

Output:

10
9
8
6
3
1

Rather than iterating backward on a specific array, we've defined the "moving backward" iteration behaviour for any array, without changing the order or producing a clone.

  • Another use case is a music player:-
    Iteration behaviour can be seen in a music player. The majority of us do not listen to songs in chronological sequence. Instead, we use shuffle and repeat to manage the playback.
    As a programmer, this entails creating a system that can play songs endlessly (repeat), randomly (shuffle), or both.
    Again, Symbol.iterator is used to implement the logic that underpins various playback choices.
    Now, what’s next?

Since we’ve covered a lot about Iterables in javascript, it’s time to warp up the session with some faqs.

Frequently asked questions 

  1. What is the iterable protocol in javascript?
    The iterable protocol enables JavaScript objects to define or customise iteration behaviour, such as which values are looped over in a for...of loop. Some built-in types, such as Array or Map, are built-in iterables with default iteration behaviour, although others (such as Object) are not.
  2. What exactly are iterators in JS?
    An iterator is a JavaScript object that defines a series and, ideally, a return value when it is terminated.
  3. Explain the significance of “=”, “==”, and “===” in javascript.
    In JavaScript, = is used to assign values to variables, while == is used to compare two variables regardless of data type. === is used to compare two variables, but it checks strict type, which checks datatype and compares two values.
  4. What makes a data structure in javascript iterable?
    The for..of loop must be able to iterate through a data structure to be called iterable.

Key takeaways 

To bind up the session, we've discussed what Iterators and Iterables in javascript are. We've also looked at various methods to make a non-iterable object iterable. It's imperative to note that Iterable is an object, which can iterate over, and an Iterator is an object that is used to iterate over an iterable object. 

I hope this introduction helped you learn more about JavaScript's internals for data structures like objects.

Don't stop here Ninja, keep your eye on excellent articles and spark your development journey. 

Happy learning!

Was this article helpful ?
0 upvotes

Comments

No comments yet

Be the first to share what you think