TypeScript - Iterators and Generators



In TypeScript, iterators and generators allow to control the iteration over the iterables. Here, iterables are objects like arrays, tuples, etc. through which we can iterate. Using iterators and generators in the code allows us to write efficient and readable code.

Here, we will discuss how to create custom iterators and generators in TypeScript.

Iterators

Iterators are used to traverse through the iterable objects. It is a unique function that returns the iterator object. The iterator object contains the next() method, which again returns the object having below 2 properties.

  • value: The value property contains the value of the next element in the sequence.

  • done: The done property contains the boolean value, representing whether the iterator reaches the end of the sequence or not.

Let's look at the below examples of iterators.

Example: Using the values() Method

In the code below, we have defined the 'fruits' array containing the string. The 'fruits.values()' returns an iterator object, which is stored in the 'iterator' variable.

Whenever we call the next() method, it returns the object containing the 'value' and 'done' properties. You can see all the values of the array. Whenever we call the next() method 6th time, it returns the object having 'value' property with an undefined value as iterator reached to the end of the sequence.

// Defining a fruits array
const fruits = ['apple', 'banana', 'mango', 'orange', 'strawberry'];
// Defining an iterator
const iterator = fruits.values();

// Getting the first element
console.log(iterator.next().value); // apple
// Getting the second element
console.log(iterator.next().value); // banana
// Getting remaining elements
console.log(iterator.next().value); // mango
console.log(iterator.next().value); // orange
console.log(iterator.next().value); // strawberry
console.log(iterator.next().value); // undefined

On compiling, it will generate the following JavaScript code.

// Defining a fruits array
const fruits = ['apple', 'banana', 'mango', 'orange', 'strawberry'];
// Defining an iterator
const iterator = fruits.values();

// Getting the first element
console.log(iterator.next().value); // apple
// Getting the second element
console.log(iterator.next().value); // banana
// Getting remaining elements
console.log(iterator.next().value); // mango
console.log(iterator.next().value); // orange
console.log(iterator.next().value); // strawberry
console.log(iterator.next().value); // undefined

Output

The output of the above example code is as follow –

apple
banana
mango
orange
strawberry
undefined

Example: Creating the Custom Iterator Function

In the code below, createArrayIterator() function is a custom iterator function.

We have started with defining the 'currentIndex' variable to keep track of the index of the element in the iterable.

After that, we return the object containing the 'next' property from the function. The 'next' property contains the method as a value, which returns the object containing the 'value' and 'done' property. The assigned value into the 'value' and 'done' properties is based on the current element in the sequence.

After that, we used the createArrayIterator() function to traverse through the array of numbers.

// Custom iterator function
function createArrayIterator(array: number[]) {
    // Start at the beginning of the array
    let currentIndex = 0;

    // Return an object with a next method
    return {
        // next method returns an object with a value and done property
        next: function () {
            // Return the current element and increment the index
            return currentIndex < array.length ?
                { value: array[currentIndex++], done: false } :
                { value: null, done: true };
        }
    };
}

// Create an iterator for an array of numbers
const numbers = [10, 20, 30];
const iterator = createArrayIterator(numbers);

console.log(iterator.next().value); // 10
console.log(iterator.next().value); // 20
console.log(iterator.next().value); // 30
console.log(iterator.next().done);  // true

On compiling, it will generate the following JavaScript code.

// Custom iterator function
function createArrayIterator(array) {
    // Start at the beginning of the array
    let currentIndex = 0;
    // Return an object with a next method
    return {
        // next method returns an object with a value and done property
        next: function () {
            // Return the current element and increment the index
            return currentIndex < array.length ?
                { value: array[currentIndex++], done: false } :
                { value: null, done: true };
        }
    };
}
// Create an iterator for an array of numbers
const numbers = [10, 20, 30];
const iterator = createArrayIterator(numbers);
console.log(iterator.next().value); // 10
console.log(iterator.next().value); // 20
console.log(iterator.next().value); // 30
console.log(iterator.next().done); // true

Output

The output of the above example code is as follows –

10
20
30
true

Generators

Generator functions are also similar to the iterators, which return the values one by one rather than returning all values once. When you call the generator function, that returns the generator object which can be used to get values one by one.

Generator functions are mainly useful when you want to get values one by one rather than getting all values at once and storing them in the memory.

Syntax

Users can follow the syntax below to create generator function in TypeScript.

function* func_name() {
    yield val;
  }
const gen = numberGenerator(); // "Generator { }"
console.log(gen.next().value); // {value: val, done: false}
  • In the above syntax, we have used the 'function*' to define the generator function.

  • You can use the 'Yield' keyword to return values one by one from the generator function.

  • When you call the generator function, it returns the generator object.

  • When you call the next() method, it returns the object containing the 'value' and 'done' properties same as the iterator.

Example: Basic Generator Function

In the code below, the numberGenerator() function is a generator function. We have used the 'yield' keyword and returned 10, 20, and 30 values one by one.

After that, we called the numberGenerator() function which returns the generator object. To get the values, we use the next() method of the generator object.

// Basic generator function
function* numberGenerator() {
    yield 10;
    yield 20;
    yield 30;
}

// Create a generator object
const gen = numberGenerator();

// Call the generator function
console.log(gen.next().value); // 10
console.log(gen.next().value); // 20
console.log(gen.next().value); // 30
console.log(gen.next().done);  // true

On compiling, it will generate the same JavaScript code.

Output

The output of the above example code is as follows –

10
20
30
true

Example: Creating the Generator Function to Traverse a Range

Here, we have defined the range() generator function which takes the starting and ending point of the range as a parameter. In the function, we traverse the range and return the values one by one using the 'yield' keyword.

After that, we used the range() function with the 'for loop' to traverse the generator object returned from the range() function. The loop prints each value returned from the range() function.

// Generators are functions that allow to traverse a range
function* range(start: number, end: number) {
    // Loop through the range
    for (let i = start; i <= end; i++) {
        // Yield the current value
        yield i;
    }
}

// Loop through the range
for (const num of range(1, 5)) {
    console.log(num); // 1, 2, 3, 4, 5
}

On compiling, it will generate the following JavaScript code.

// Generators are functions that allow to traverse a range
function* range(start, end) {
    // Loop through the range
    for (let i = start; i <= end; i++) {
        // Yield the current value
        yield i;
    }
}

// Loop through the range
for (const num of range(1, 5)) {
    console.log(num); // 1, 2, 3, 4, 5
}

Output

1
2
3
4
5

Difference Between Iterators and Generators

Iterators and generators look similar. However, they are different. Here, we have explained some differences between both.

Feature Iterator Generator
Definition An object that adheres to the Iterator protocol, specifically implementing a next() method. A function that can pause execution and resume, automatically managing the state internally.
Control Mechanism Manually controls iteration via the next() method, which returns { value, done }. Uses yield to pause and return values, and next() to resume.
Syntax Typically involves creating an object with a next() method. Defined with function* syntax and includes one or more yield statements.
Usage Complexity Higher, due to explicit state management and the need for a custom next() implementation. Lower, as state management and iteration control are simplified by yield.
Ideal Use Cases Suitable for simple, custom iterations where explicit control is required. Better for complex sequences, asynchronous tasks, or when leveraging lazy execution.
Advertisements