JavaScript Iterator and Generator [In-Depth Tutorial]

JavaScript Iterator and Generator [In-Depth Tutorial]

Introduction to JavaScript Iterators and Generators

In JavaScript, an iterator is an object that defines a sequence and a return value for that sequence. It allows you to iterate over a collection of values, such as an array or an object, and perform a specific operation on each value. Iterators are a powerful feature of the language that can greatly simplify the process of working with collections of data.

A generator is a special type of function that can be used to create an iterator. It allows you to define a sequence of values and pause the execution of the function at any point, returning a value and saving the state of the function. This makes generators a useful tool for implementing iterators and creating sequences of data that can be iterated over.

In this article, we will explore the concepts of iterators and generators in JavaScript and discuss how they can be used in your code.


How Iterators work?

Thefor/ofloop and spread operator work seamlessly with iterable objects, but it is worth understanding what is actually happening to make the iteration work. There are three separate types that you need to understand to understand iteration in JavaScript.

  • First, there are theiterableobjects: these are types like Array, Set, and Map that can be iterated.
  • Second, there is theiteratorobject itself, which performs the iteration.
  • And third, there is theiteration resultobject that holds the result of each step of the iteration.

An iterator has two main methods: next() and return(). The next() method returns an object with two properties: value and done. The value property is the next value in the sequence, and the done property is a boolean that indicates if the iterator has reached the end of the sequence. When the iterator is finished, the done property will be true and the value property will be undefined.

Here is an example of a simple iterator that iterates over an array of numbers:

const numbers = [1, 2, 3, 4];

const numbersIterator = {
    index: 0,
    next: function () {
        if (this.index < numbers.length) {
            return { value: numbers[this.index++], done: false };
        } else {
            return { done: true };
        }
    },
};

To use this iterator, we can call the next() method repeatedly until the done property is true.

let result = numbersIterator.next();
while (!result.done) {
    console.log(result.value);
    result = numbersIterator.next();
}

Output

1
2
3
4

This will output the numbers 1 through 4 to the console.


How Generator works?

A generator is a special type of function that can be paused and resumed. When a generator function is called, it does not execute the function body immediately. Instead, it returns a generator object that can be used to execute the function body.

A generator function is defined using the function* syntax. When you invoke a generator function, it does not actually execute the function body, but instead returns a generator object. This generator object is an iterator. Calling itsnext()method causes the body of the generator function to run from the start (or whatever its current position is) until it reaches ayieldstatement.

yieldis new in ES6 and is something like areturnstatement. The value of theyieldstatement becomes the value returned by thenext()call on the iterator.

Here is an example of a generator function that yields the numbers 1 through 5:

function* numbersGenerator() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}

const numbers = numbersGenerator();

To execute the generator function, we can call the next() method on the generator object.

console.log(numbers.next().value);
console.log(numbers.next().value);
console.log(numbers.next().value);

Output

1
2
3

Example-1: Accept arguments with Generators

Generators can also accept arguments and return values using the return() method.

function* addGenerator(x) {
    const y = yield;
    return x + y;
}

const add = addGenerator(5);
console.log(add.next());
console.log(add.next(7));

Output

{ value: undefined, done: false }
{ value: 12, done: true }

Example-2: Using waitForPromise with Generators

Generators can be used to simplify asynchronous code by allowing the function to pause and wait for a promise to resolve. For example, here is a generator function that waits for a promise to resolve before continuing execution

function* waitForPromise(promise) {
    const result = yield promise;
    return result;
}

const wait = waitForPromise(Promise.resolve(10));
wait.next().value.then((result) => console.log(wait.next(result).value));

Output

10

Example-3: Create infinite sequences

Generators can also be used to create infinite sequences using the yield* keyword. The yield* keyword allows the generator to delegate to another generator or iterable object.

Here is an example of a generator that yields an infinite sequence of numbers

function* countGenerator() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

const count = countGenerator();
console.log(count.next().value); 
console.log(count.next().value); 
console.log(count.next().value);

Output

0
1
2

Example-4: Create custom iterators

Generators can be used to create custom iterators, as they have a built-in next() method. Here is an example of a generator that acts as an iterator for an array

function* arrayIterator(array) {
    for (const value of array) {
        yield value;
    }
}

const iterator = arrayIterator([1, 2, 3]);
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);

Output

1
2
3

Example-5: Return value of Generator function

The return value of thenext()function is an object that has avalueproperty and/or adoneproperty. With typical iterators and generators, if thevalueproperty is defined, then thedoneproperty is undefined or isfalse. And ifdoneistrue, thenvalueis undefined. But in the case of a generator that returns a value, the final call tonextreturns an object that has bothvalueanddonedefined. Thevalueproperty holds the return value of the generator function, and thedoneproperty istrue, indicating that there are no more values to iterate. This final value is ignored by thefor/ofloop and by the spread operator, but it is available to code that manually iterates with explicit calls tonext():

function *oneAndDone() {
    yield 1;
    return "done";
}

// The return value does not appear in normal iteration.
[...oneAndDone()]   // => [1]

// But it is available if you explicitly call next()
let generator = oneAndDone();
generator.next()           // => { value: 1, done: false}
generator.next()           // => { value: "done", done: true }
// If the generator is already done, the return value is not returned again
generator.next()           // => { value: undefined, done: true }

Summary

In summary, iterators and generators are useful tools in JavaScript for creating and iterating over sequences of values. They can be used to simplify asynchronous code and create custom iterators.

Let’s summarise what we have learned:

  • An iterator object has anext()method that returns an iteration result object.
  • An iteration result object has avalueproperty that holds the next iterated value, if there is one. If the iteration has completed, then the result object must have adoneproperty set totrue.
  • Generator functions (functions defined withfunction*instead offunction) are another way to define iterators.
  • When you invoke a generator function, the body of the function does not run right away; instead, the return value is an iterable iterator object. Each time thenext()method of the iterator is called, another chunk of the generator function runs.
  • Generator functions can use theyieldoperator to specify the values that are returned by the iterator. Each call tonext()causes the generator function to run up to the nextyieldexpression.
  • The value of thatyieldexpression then becomes the value returned by the iterator. When there are no moreyieldexpressions, then the generator function returns, and the iteration is complete.

References

Iterators and generators - JavaScript | MDN (mozilla.org)

Olorunfemi Akinlua

Olorunfemi Akinlua

Boasting over five years of experience in JavaScript, specializing in technical content writing and UX design. With a keen focus on programming languages, he crafts compelling content and designs user-friendly interfaces to enhance digital experiences across various domains.