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 arrayconst fruits =['apple','banana','mango','orange','strawberry'];// Defining an iteratorconst iterator = fruits.values();// Getting the first elementconsole.log(iterator.next().value);// apple// Getting the second elementconsole.log(iterator.next().value);// banana// Getting remaining elementsconsole.log(iterator.next().value);// mangoconsole.log(iterator.next().value);// orangeconsole.log(iterator.next().value);// strawberryconsole.log(iterator.next().value);// undefined
On compiling, it will generate the following JavaScript code.
// Defining a fruits arrayconst fruits =['apple','banana','mango','orange','strawberry'];// Defining an iteratorconst 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 functionfunctioncreateArrayIterator(array:number[]){// Start at the beginning of the arraylet currentIndex =0;// Return an object with a next methodreturn{// next method returns an object with a value and done propertynext:function(){// Return the current element and increment the indexreturn currentIndex < array.length ?{ value: array[currentIndex++], done:false}:{ value:null, done:true};}};}// Create an iterator for an array of numbersconst numbers =[10,20,30];const iterator =createArrayIterator(numbers);console.log(iterator.next().value);// 10console.log(iterator.next().value);// 20console.log(iterator.next().value);// 30console.log(iterator.next().done);// true
On compiling, it will generate the following JavaScript code.
// Custom iterator functionfunctioncreateArrayIterator(array){// Start at the beginning of the arraylet currentIndex =0;// Return an object with a next methodreturn{// next method returns an object with a value and done propertynext:function(){// Return the current element and increment the indexreturn currentIndex < array.length ?{ value: array[currentIndex++], done:false}:{ value:null, done:true};}};}// Create an iterator for an array of numbersconst 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 functionfunction*numberGenerator(){yield10;yield20;yield30;}// Create a generator objectconst gen =numberGenerator();// Call the generator functionconsole.log(gen.next().value);// 10console.log(gen.next().value);// 20console.log(gen.next().value);// 30console.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 rangefunction*range(start:number, end:number){// Loop through the rangefor(let i = start; i <= end; i++){// Yield the current valueyield i;}}// Loop through the rangefor(const num ofrange(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 rangefunction*range(start, end){// Loop through the rangefor(let i = start; i <= end; i++){// Yield the current valueyield i;}}// Loop through the rangefor(const num ofrange(1,5)){
console.log(num);// 1, 2, 3, 4, 5}</code></pre>
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.
Leave a Reply