DEV Community

Akshendra Pratap Singh
Akshendra Pratap Singh

Posted on

Iterators in JavaScript

Many a times you wake up and realize that today you will be traversing through an array or maybe many arrays. But you don't worry about it, you have done it before and it wasn't that difficult. You also have a lot of options, you can use the good old loops, or the wonderful map, reduce, fitler. OR you can use the iterators.

Iterator is a design pattern that allows us to traverse over a list or collection. In JavaScript, like most things, they are implemented as objects. Before going in detail, here is a simple example.

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

for (const num of arr) {
  console.log(num);
}
Enter fullscreen mode Exit fullscreen mode

Using for..of loop, you can iterate over any object that implements the iterable protocol.

Iterable Protocol

To follow this protocol, the object must define a special method @@iterator (as Symbol.iterator key) which takes zero arguments and returns an object which itself should follow the iterator protocol.

Iterator Protocol

To follow this protocol, the object must define a method named next, which itself returns an object with two properties:

  1. value: the current item in iteration
  2. done: a boolean, that represents whether the iteration is finished or not. done=true means iteration is finished.

Array, String, Map, Set, TypedArrays follow the iterator protocol.

Implementing the protocols

Here is a function that returns an iterable which allows us iterate over first n natural numbers.

function numbers(till = 100) {
  let i = 0;
  const iteratorFx = () => {
    const iterator = {
      next() {
        i += 1;
        if (i <= till) {
          return { done: false, value: i };
        }
        return { done: true };
      },
    };
    return iterator;
  };
  return {
    [Symbol.iterator]: iteratorFx,
  };
}

const numbersTill10 = numbers(10);
for (const i for numbersTill10) {
  // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}
Enter fullscreen mode Exit fullscreen mode

The @@iterator method is only called once at the beginning of the for..of loop. So the following is same as above

for (const i for numbers(10)) {
  // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
}
Enter fullscreen mode Exit fullscreen mode

Since we know that the object has a next method which implements the details of iteration, we can simple call this method ourselves.

const numbersTill5 = number(5);
numbersTill5.next(); // { done: false, value : 1 }
numbersTill5.next(); // { done: false, value : 2 }
numbersTill5.next(); // { done: false, value : 3 }
numbersTill5.next(); // { done: false, value : 4 }
numbersTill5.next(); // { done: false, value : 5 }
numbersTill5.next(); // { done: true }
Enter fullscreen mode Exit fullscreen mode

This can be used to control the iteration externally, by passing values to next method.

We can implement our custom iterators like above. However, JavaScript provides another way to create iterables.

Generators

Generators are special function which when called return a Generator object. The generator object follows the iteration protocols. So to implement the above example using generators,

function* generateNumbers(till = 100) {
  let i = 1;
  while (i <= till) {
    yield i;
    i += 1;
  }
}

const numbersTill10 = generateNumbers(10); // iterator
// rest is same
Enter fullscreen mode Exit fullscreen mode

The value sent by yield (here i), will be the value stored in object returned by the next method. And when the generator finishes it returns { done: true }.

It is very clear from the above example that Generators provide a concise way to create iterables. They abstract away the protocols, and we need to worry about the iteration logic only.

Conclusion

Since we started this post with a hyperbole about traversing array. Its only fair that we include an example involving arrays. Arrays are already iterable, so we will create an iterable value mapper.

function* mapOver(arr, mapper = (v) => v) {
  for (let i = 0; i < arr.length; i += 1) {
    yield mapper(arr[i]);
  }
}

const twices = mapOver([...numbers(5)], (v) => v + 2);
for (const num of twices) {
  // 2, 4, 6, 8, 10
}
Enter fullscreen mode Exit fullscreen mode

Spread (...) operator, by definition works on iterables.

Top comments (2)

Collapse
 
victor profile image
Victor.

What will be a real implementation for iterators?

Collapse
 
kepta profile image
Kushan Joshi • Edited

If you are talking about a real application for iterators, I find it incredibly useful to do paged requests.
These requests are requests which only give a certain chunk of data corresponding to the page. Some API's also do not provide a way to see how many pages are there in total.

Example

async function* fetchTweet() {
  let page = 0;
  let response;
  while (true) {
    response = await fetch('https://twitter.com/latest?page=' + page++);
    if (response.length === 0) {
      break;
    }
    yield response;
  }
}

async function getAllTweets() {
  for (const result of await getAllTweets()) {
    // do something one by one with result
  }

  // handle all of them together
  const allTweets = await Promise.all([...getAllTweets()]);
}

I wrote a similar article on Iterators, feel free to check it out dev.to/kepta/how-i-learned-to-stop...