loading...

How I learned to Stop Looping and Love the Iterator

kepta profile image Kushan Joshi Updated on ใƒป5 min read

Let us all take out a minute from our busy lives to look at past and think how many hours did we waste fixing that for loop. If your memory returns an empty array have a look at this one. (Spoiler alert!, it sums the two consecutive elements of an array and for the first element it pairs it with the last element.)

 for (var i = 0, len = grid.length, j = len - 1, p1, p2; i < len; j = i++) {
    p1 = grid[i];
    p2 = grid[j];
    sum += p1 + p2;
}

The Problem with for loop

The biggest problem, in my opinion, is that they are too powerful. You can easily end up with a wrong for loop configuration which might deceivingly work, only to explode later in your production stack.

In short, the surface area of for loop is so wide that it is a bug magnet.

Functional Alternatives

90% of the times you can always convert that good old for loop into a nice good looking .map/.reduce. There are a very few places where things could go wrong with this approach.

for (var i = 0; i < array.length; i++) {
    array[i] = array[i] * 2;
}

array = array.map(o => o * 2);

The Iterable Approach

A functional alternative should be your first choice in getting rid of for loops, but there are times when you want to stick to it:

  1. You feel performance is critical. For loop is still fastest but only by a very small margin.
  2. You want to break;/continue; out of loop easily.
  3. Certain complicated objects (Sets, Map, NodeList) don't really have a straightforward way of looping through them.

The smart ECMAScript folks knew about these limitations and probably many more, so they came up with an iteration protocol in ES2015.

What is a protocol anyway?

Javascript doesnโ€™t have formal protocols like other languages. Think of it as a convention, like how node programmers like to follow error first callback.

Introducing iterables

Iterables are everywhere in javascript, you have been using them unknowingly. Anything which has a Symbol.iterator property is iterable.

Let us look at the simplest iterable, a string!


str = 'hello world';
iterator = str[Symbol.iterator](); // StringIterator {}
iterator.next(); // gives `h`
...
...
iterator.next(); // gives `d`
iterator.next(); // gives `undefined` as no more string left.

string type in javascript comes baked in with iteration protocol, which means we can now say strings are iterable.

What is Symbol and Symbol.iterator?

This topic is worthy of an article itself, but in short Symbol solves the problem of sneaking in a property into an object which you do not want to clash with any existing property. Visit MDN web docs for more info

So no more putting @@@@@\_my\_hidden\_property\_ in your objects!

Symbol.iterator is a globally available constant for anyone to use and implement the iteration protocol. So you can use it to make your own object implement iteration.

How do I implement the iteration protocol for my custom objects?

class Rand {
  [Symbol.iterator] () {
    let count = 0;
    return {
      next: () => ({ 
        value: count++, 
        done: count > 5
      })
    };
  }
}
var rand = new Rand();
var iterator = rand[Symbol.iterator]();
iterator.next();// {value: 0, done: false}
iterator.next();// {value: 1, done: false}
// .. 
iterator.next();// {value: 5, done: false}
iterator.next();// {value: undefined, done: true}

Don't let the syntax bog you down. Let us break this example down:

  • [Symbol.iterator] () This weird looking syntax is nothing but a new ES2015 way of initializing properties dynamically. (Look here more information.)
  • The Symbol.iterator method must return an object { next } (Don't forget this is all a convention/protocol). We call this object iterator. (More on this in the next section)
  • The .next() is simply incrementing the count every time it is called and it toggles done to true when the count exceeds 5.

What is the difference between iterable & iterator ?

Repeat after me,

  • Iterable is an object which implements the iteration protocol. string, Array, Set, Map are all iterables!
class Rand {
  [Symbol.iterator] () { // Rand has `Symbol.iterator` method, hence it is an iterable!
    let count = 0;
    return { // The return value is called an `iterator`
      next: () => ({ 
        value: count++, 
        done: count > 5
      })
    };
  }
}
  • Iterator is the thing which is returned by [Symbol.iterator]() of an iterable.
    • It contains useful state information about where the current iteration is and what value to provide next.
    • Any iterator must have a .next method on it (remember this is all a convention?), which would be used to get the next value out of it.
    • The object returned by the .next() method must be {value, done}, where value is the current value and done tells whether iteration has finished or not.
var iterator = rand[Symbol.iterator](); // I am an iterator

iterator.next(); //  {value: 0, done: false}
iterator.next(); //  {value: 1, done: false}
...
iterator.next(); //  {value: 4, done: false}
iterator.next(); //  {value: undefined, done: true}

What do I get out of this complicated protocol?

You get a lot of superpowers for free if you enable iteration in your custom object or use any of Javascript's inbuilt iterables like Array, string, Map or Set.

1. Super Power: Spread it

Remember the class Rand that we just defined above? Since it is an iterable it inherits the spreading super powers. Visit MDN web docs for more info on spread.

var rand = new Rand();
var myArray = [...rand]; // [0, 1, 2, 3, 4] 

// string can also be used since it is an iterable
[..."kushan"]; // ["k", "u", "s", "h", "a", "n"]

Note: We didn't do [...rand[Symbol.iterator]()], since ... expects an iterable and not iterator.

2. Super Power: Use Array.from

Array.from(rand); // [0, 1, 2, 3, 4]
Array.from("kushan"); // ["k", "u", "s", "h", "a", "n"]

3. Super Power: for of loop

for of is a new looping mechanism introduced in ES2015, which only understands iterables. It automagically calls Symbol.iterator, stores the iterator behind the scenes and calls .next for you. It also stops when the iterator returns {done:true}.

for(const v of rand) {
  console.log(v); 
}
/*Output*/
// 0
// 1 
// ..
// 4

Unlike spread operator, for of loop can accept both iterable or iterator.

var map = new Map([['a', 1], ['b', 2]]); 

map[Symbol.iterator];// map is iterable because it has the `Symbol.iterator` key 

// `for of` loop understands `iterable`
for (const [key, val] of map) { 
    console.log(key, val); // 'a', 1
}

// `for of` loop also understands iterators
var iterator = map[Symbol.iterator](); // returns an iterator
for (const [key, val] of iterator) {
    console.log(key, val); // 'a', 1
}

// .keys() is a part of `Map` api
var keyIterator = map.keys();   // returns an iterator
for (const key of keyIterator) {
    console.log(key); // 'a'
}
// .values() is a part of `Map` api
var valueIterator = map.values();   // returns an iterator
for (const val of valueIterator) {
     console.log(val); // 1'
}

4. Super Power: Destructuring

This is one of my favourite for demoing super powers of iterables. ES2015 introduced destructuring assignment, which is built on top of iterables. You can only destruct iterables!

// array is iterable
[a, b] = [10, 20]; // a=10, b=20

// our custom class rand is Destructable :P
[a, b, c] = rand; // a = 0, b = 1, c = 2

// you can do fancy things like
[a, ...b] = rand; // a = 0, b = [1, 2, 3, 4]

Summary

Please share any other super power that you get for free when using iterables. I hope this article helped you understand iterables & iterators.

Don't forget to check out my previous articles.

If you โค๏ธ this article, please share this article to spread the words.

Posted on by:

kepta profile

Kushan Joshi

@kepta

I <3 Javascript and ๐Ÿ‡ฎ๐Ÿ‡ณ

Discussion

pic
Editor guide
 

Thanks @kepta for the wonderful article.
I am able to see "why" iterators be so useful with those 4 "superpowers" ๐Ÿ˜€

Regarding iterator vs iterables, the way I understood is that,

  1. iterables are objects that can be iterated over
  2. so that means iterable objects return an iterator object with which we can iterate.

Did I understand it correctly?

 

You got that damn right !

 

Thanks Kushan ๐Ÿ‘

 
 

Thanks for the article....learned from it

 

Very helpful, thank you Kushan ๐Ÿ‘Œ