Deep dive into JavaScript's Iterators, Iterables and Generators
Tags: javascript, webdev
Published: 2022-08-08 21:24:21

by [Nwakor Chidinma Favour](https://blog.openreplay.com/authors/nwakor-chidinma-favour)

The default operations like `for...of` loop in JavaScript are powered behind the scene by the concepts of  **Iterators**, **Iterables**, and **Generators**. These concepts serve as the protocol for iterating over data structures provided by JavaScript, such as the `for...of` loop, used to traverse the elements of specific data structures one by one.

Throughout this article, we'll discuss and explore these concepts and learn how to implement them within your next JavaScript project. 

## Symbols for iterators

But before we explain these concepts, we'll take a short discovery on the JavaScript built-in method, [`Symbol.iterator()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator),  which serves the unit block on implementing custom iteration in JavaScript. 

```javascript
// syntax
Symbol.iterator() // Symbol(Symbol.iterator)
```

The concept of an [**Iterable** ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol) is any structure that has the `Symbol.iterator` key; the corresponding method has the following behavior:

- When the `for..of` loop begins, it first looks for errors. If none are found, it then accesses the method and the object that the method is defined on.
- The object will then be iterated in a `for..of` loop manner.
- Then, it uses the `next()` method of that output object to get the next value to be returned.
- The values given back will have the format `done:boolean, value: any`. The loop is finished when `done:true` is returned.

```javascript
let list = {
  start: 2,
  end: 7,
};

list[Symbol.iterator] = function () {
  return {
    now: this.start,
    end: this.end,
    next() {
      if (this.now <= this.end) {
        return { done: false, value: this.now++ };
      } else {
        return { done: true };
      }
    },
  };
};

for (let i of list) {
  console.log(i);
}

//output
2
3
4
5
6
7
```

In JavaScript, the `Array` object is the most iterative `iterable` in nature. Still, any object does possess the nature of a group of the elements—expressing the nature of a range like an integer between 0 and `Infinity`, can be turned into an `iterable`. And the scope of this article would be to help you understand and implement custom iterables.
    

## Iterator and Iterable in JavaScript

Iteration on any `array` of data involves the use of the traditional `for` loop to iterate over its elements as below:

```javascript
let items = [1, 2, 3];

for (let i = 0; i < items.length; i++) {
    console.log(items[i]);
}

//output
'1'
'2'
'3'
```

The above code construct works fine but poses complexity when you nest loops inside loops. This complexity will result from trying to keep track of the multiple variables involved in the loops.

To avoid the mistakes brought on by remembering loop indexes and reduce the loop's complexity, [ECMAScript 2015](https://262.ecma-international.org/6.0/) developed the `for...of` loop construct.

This gives birth to cleaner code, but most importantly, the `for...of` loop provides the capacity to iterate/loop over not just an array but any `iterable` object.

An [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol) is  any object which implements the `next()` method that returns an object with two possible key-value pairs:

```javascript
{ value: any, done: boolean }
```

Using the earlier discussion on the `Symbol.iterator()`, we know how to implement the `next()`  method that accepts no argument and returns an object which conforms to the above key-value pairs.

With this in mind, we implement a custom iteration process for a custom object type and successfully use the `for…of` loop to carry out iteration on the type. The following code block will attempt to create a `LeapYear` object that returns a list of leap years in the range of ( `start`, `end`) with an `interval` between subsequent leap years.

```javascript
class LeapYear {
  constructor(start = 2020, end = 2040, interval = 4) {
    this.start = start;
    this.end = end;
    this.interval = interval;
  }

  [Symbol.iterator]() {
    let nextLeapYear = this.start;
    return {
      next: () => {
        if (nextLeapYear <= this.end) {
          let result = { value: nextLeapYear, done: false };
          nextLeapYear += this.interval;
          return result;
        }
        return { value: undefined, done: true };
      },
    };
  }
}
```

In the above code, we implemented the `Symbol.iterator()` method for the custom type `LeapYear`. We have the starting and ending points of our iteration in the `this.start` and `this.end` fields, respectively. Using the `this.interval`, we keep track of the interval between the first element and the next element of our iteration.

Now, we can call the `for…of` loop on our custom type and see its behavior and output values like a default array type.

```javascript
let leapYears = new LeapYear();

for (const leapYear of leapYears) {
    console.log(leapYear);
}

// output
2020
2024
2028
2032
2036
2040
```

As stated earlier, `Iterable's are JavaScript structures with the `Symbol.iterator()` method, like `Array`, `String`, `Set`. And as a result, our above LeapYear type is an `Iterable` too. This is a basic idea of `Iterables` and `Iterators`; for more reading, refer [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators).



## Open Source Session Replay

_[OpenReplay](https://github.com/openreplay/openreplay) is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data._

![replayer.png](https://static.openreplay.com/images/banner-blog.png)

_Start enjoying your debugging experience - [start using OpenReplay for free](https://github.com/openreplay/openreplay)._




## Generators in Javascript

A  [`generator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) is a function that can produce many output values, and may be paused and resumed. In JavaScript, a generator is like a traditional function that produces an **iterable** `Generator` object. This behavior is opposed to traditional JavaScript functions, which execute fully and return a single value with a `return` statement. The **Generator** uses the new keyword, `yield`, to pause and return a value from a generator function. For more reading, refer [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols).

Below is the basic syntax for the declaration of a `Generator`:

```javascript
function* generator() {} // Object [Generator] {}
```

Let's explore how `generators` and `yield` work. 

```javascript
function* generate() {
  console.log("invoked first time");
  yield "first";
  console.log("invoked second time");
  yield "second";
}

let gen = generate();
let next_value = gen.next();
console.log(next_value);

// output
// <- invoked first time
// <- {value: 'first', done: false}

next_value = gen.next();
console.log(next_value);

// output
// <- invoked second time
// <- {value: 'second', done: false}
```
    
The `Generator` function yields an object similar to the `Symbol.iterator` two properties: `done` and `value` fields. In the context of our discussion, this provides that the `Generator` type is an `Iterable`. This means we can use the `for…of` loop on it to iterate through its returned values. The `for…of` loop enables us to return the value inside the `value` field without the need to return the `done` field as the `for.of` loop keeps track of the iteration. The `for.of` loop is a sugar syntax abstracting the implementation used in the previous code block.


```javascript
function* generate() {
  console.log("invoked first time");
  yield "first";
  console.log("invoked second time");
  yield "second";
}

let gen = generate();

for (let value of gen) {
  console.log(value);
}

// output
//invoked first time
//first
//invoked second time
//second
```

Alternatively, we can also use the concept of `Generator` with our `LeapYear` type to implement iteration on it. See below code block for the updated code snippet:

```javascript
class LeapYear {
    constructor(start = 2020, end = 2040, interval = 4) {
      this.start = start;
      this.end = end;
      this.interval = interval;
    }
    *[Symbol.iterator]() {
      for (let index = this.start; index <= this.end; index += this.interval) {
        yield index;
      }
    }
  }
```

The above refactored and succinct code will produce the same result as earlier:

```
// output
2020
2024
2028
2032
2036
2040
```

From the above output, using the `Symbol.iterator` method is much simpler than using the `Generator` concept. The above code snippets were used to implement a `LeapYear` iterator to generate leap years from the year 2020 to the year 2040.





## Generator vs. Async-await — AsyncGenerator

It is possible to simulate the asynchronous behavior of code that "waits," a pending execution, or code that appears to be synchronous even if it is asynchronous, using both the `generator/yield` concepts to imitate the [`async/await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) functions. A generator function's iterator (the `next` method) executes each yield-expression one at a time instead of async-await, which executes each `await` sequentially. For `async/await` functions, the return value is always a promise that will either **resolve** to an `any` value or throws an error. In contrast, the return value of the generator is always `{value: X, done: Boolean}`, and this could make one conclude that one of these functions is built off the other. Understanding that an async function may be broken down into a generator and a promise implementation is helpful. Tada!

Now, combining generator with `async/await` gives birth to a new type, [`AsyncGenerator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator). In contrast to a conventional generator, an async generator's `next()` method returns a `Promise`. You use the `for await...of` construct to iterate over an async generator.

The below code snippet would attempt to asynchronously fetch external data when requested and yield the value.

```javascript
let user = {
  request: 0,
  trials: 12,
};

async function* fetchProductButton() {
  if (user.trials == user.request) {
    return new Error("Exceeded trials");
  }
  let res = await fetch(`https://dummyjson.com/products/${user.request + 1}`);
  let data = await res.json();
  yield data;
  user.request++;
}

const btn = fetchProductButton();

(async () => {
  let product = await btn.next();
  console.log(product);
  // OR
  for await (let product of btn) {
    console.log(product);
  }
})();
```
    
The above code snippet will return the below JSON data:

```javascript
//output
{
  value: {
    id: 1,
    title: 'iPhone 9',
    description: 'An apple mobile which is nothing like apple',
    price: 549,
    discountPercentage: 12.96,
    rating: 4.69,
    stock: 94,
    brand: 'Apple',
    category: 'smartphones',
    thumbnail: 'https://dummyjson.com/image/i/products/1/thumbnail.jpg',
    images: [
      'https://dummyjson.com/image/i/products/1/1.jpg',
      'https://dummyjson.com/image/i/products/1/2.jpg',
      'https://dummyjson.com/image/i/products/1/3.jpg',
      'https://dummyjson.com/image/i/products/1/4.jpg',
      'https://dummyjson.com/image/i/products/1/thumbnail.jpg'
    ]
  },
  done: false
}
```

The above code snippet would be a real-life example of simulating session access to always get a value as long as your session access isn't exhausted. Using the async generator provides flexibility as it could help make paginated requests to an external API or help decouple business logic from the progress reporting framework. Also, using a [`Mongoose cursor`](https://mongoosejs.com/docs/api/querycursor.html) to iterate through all documents while updating the command line or a [`Websocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) with your progress.




## Built-in APIs accepting iterables

A wide variety of APIs supports iterables. Examples include the Map, WeakMap, Set, and WeakSet objects. Check out this [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#built-in_apis_accepting_iterables) document on JavaScript APIs that accepts `iterables`


## Conclusion

In this article, you have learned about the JavaScript iterator and how to construct custom iteration logic using iteration protocols. Also, we learned that `Generator` is a function/object type that yields the kind of object value as an iterator. Although they are rarely utilized, they are a powerful, flexible feature of JavaScript, capable of dealing with infinite data streams, which can be used to build infinite scrolls on the frontend of a web application, to work on sound wave data, and more. They can maintain state, offering an effective technique to create iterators. Generators may imitate the `async/await` capabilities when combined with Promises, which enables us to deal with asynchronous code in a more direct and readable way.


> A TIP FROM THE EDITOR: For more on internal details of JavaScript, check out our [JavaScript Types and Values, explained](https://blog.openreplay.com/javascript-types-and-values-explained) and [Explaining JavaScript's Execution Context and Stack](https://blog.openreplay.com/explaining-javascript-s-execution-context-and-stack) articles.


[![newsletter](https://blog.openreplay.com/static/e6b18eda7a327511296a2f27692f9c70/e9957/newsletter.png)](https://newsletter.openreplay.com/)