TypeScript, being a superset of JavaScript, supports many advanced language features, including Iterators and Generators. These are powerful tools for working with sequences of data, offering a flexible and memory-efficient way to work with large or potentially infinite data streams.
An Iterator is an object that allows you to iterate (or traverse) through a collection of data, like an array or a list. In TypeScript, an iterator is any object that implements the Iterator
interface.
An iterator has the following two main methods:
next()
: This method returns an object with two properties:value
: The current element in the collection.done
: A boolean that indicates whether the iteration has been completed.Iterators are used behind the scenes in many TypeScript and JavaScript constructs, such as for...of
loops and spread operators.
Let’s look at an example of manually creating and using an iterator in TypeScript.
function makeIterator(array: any[]): Iterator {
let index = 0;
return {
next: function () {
return index < array.length
? { value: array[index++], done: false }
: { value: undefined, done: true };
},
};
}
let iterator = makeIterator([10, 20, 30]);
console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: 30, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
makeIterator
takes an array and returns an object with the next
method.next
method returns the next element in the array until the end is reached, after which it sets done
to true
.
{ value: 10, done: false }
{ value: 20, done: false }
{ value: 30, done: false }
{ value: undefined, done: true }
for...of
The for...of
loop in TypeScript automatically works with objects that have an iterator (i.e., objects implementing the Iterator
interface). Here’s a simple example:
let numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}
1
2
3
TypeScript (and JavaScript) has built-in support for iteration over iterable objects like arrays, strings, maps, and sets.
You can also create custom iterators by defining the [Symbol.iterator]
method in your objects. Here’s how you can create an object that behaves like an array with a custom iterator:
class CustomIterable {
private data: number[];
constructor(data: number[]) {
this.data = data;
}
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
return index < data.length
? { value: data[index++], done: false }
: { value: undefined, done: true };
},
};
}
}
let iterable = new CustomIterable([100, 200, 300]);
for (let value of iterable) {
console.log(value);
}
[Symbol.iterator]()
method makes the CustomIterable
class iterable.for...of
, we can now loop over the class instances as if they were arrays.
100
200
300
A Generator in TypeScript is a special type of function that can be paused and resumed. Instead of returning a single value, a generator can yield multiple values over time. This makes generators extremely useful for working with large or infinite data sets because values are produced only when needed (lazily evaluated).
In TypeScript (and JavaScript), generators are defined using the function*
syntax, and they return an iterator object.
Here’s a basic generator function:
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
let generator = simpleGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
yield
is used to return values from the generator one by one.generator.next()
resumes the generator from where it left off.
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: undefined, done: true }
You can also pass values back into a generator using the next()
method:
function* generatorWithInput() {
const first = yield "First yield";
console.log(`Received: ${first}`);
const second = yield "Second yield";
console.log(`Received: ${second}`);
}
let generator = generatorWithInput();
console.log(generator.next()); // { value: "First yield", done: false }
console.log(generator.next("Input for first yield")); // { value: "Second yield", done: false }
console.log(generator.next("Input for second yield")); // { value: undefined, done: true }
next()
can be captured inside the generator and processed further.
{ value: "First yield", done: false }
Received: Input for first yield
{ value: "Second yield", done: false }
Received: Input for second yield
{ value: undefined, done: true }
Generators can also be used to create infinite sequences because they don’t produce all their values at once. Here’s an example:
function* infiniteNumbers() {
let num = 1;
while (true) {
yield num++;
}
}
let numbers = infiniteNumbers();
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
console.log(numbers.next().value); // 3
// This can go on indefinitely
next()
is called. There’s no end to the sequence.
1
2
3
Generators can delegate part of their operations to another generator using the yield*
expression. This is useful for modularizing complex generator operations.
function* subGenerator() {
yield "A";
yield "B";
}
function* mainGenerator() {
yield* subGenerator();
yield "C";
}
let gen = mainGenerator();
console.log(gen.next().value); // A
console.log(gen.next().value); // B
console.log(gen.next().value); // C
yield* subGenerator()
line allows mainGenerator()
to yield all values from subGenerator()
before continuing.
A
B
C
TypeScript also supports asynchronous generators using async function*
. These are useful when you need to yield values from asynchronous operations, such as fetching data from a remote server over time.
async function* asyncGenerator() {
for (let i = 1; i <= 3; i++) {
await new Promise((resolve) => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
const gen = asyncGenerator();
for await (let value of gen) {
console.log(value); // Output: 1, 2, 3 (with a delay of 1 second between each)
}
})();
async function*
allows yielding values from asynchronous operations.for await...of
loop is used to consume values from an async generator.Iterator
interface and expose a next()
method to get the next value in a sequence.We have explored Iterators and Generators in TypeScript. Iterators provide a way to traverse through data collections, while generators offer a more flexible and lazy approach by yielding values one at a time. These tools are powerful, especially when dealing with large data sets or asynchronous operations, making them essential to mastering TypeScript's capabilities. Happy Coding!❤️