Generators are an incredibly powerful tool for creating iterators in JavaScript and Typescript. A generator is a process that can be paused and resumed and can yield multiple values. Generators allow you to define an iterable sequence of values and then iterate over them with a for-of loop. Generators are useful for creating custom iterators for complex data structures, like trees or graphs. Generators can save their current state, allowing them to create efficient iterators and process an infinite amount of data.
Generators
Generators are defined by the keyword “function*” and can be used to define an iterable sequence.
// Generator function declaration
function* generatorFunction() {}
Generators are declared by using the “yield” keyword to specify the value that will be returned in each loop iteration. The generator continues looping until it encounters a “return” statement or the end of the generator body.
To read more articles like this visit Netcreed
function* generator(i) {
yield i;
yield i + 10;
}
Generators provide a concise way to define iterators without having to write a custom loop. Generators can be used to great effect when dealing with complex data structures. For example, you can use a generator to traverse a tree structure, returning each node as it is encountered. This can be particularly useful when dealing with graphs, where an iterator might need to traverse multiple edges to reach a given node.
When a Generator function is called, Instead of returning a value right away, a generator function produces an Iterable Generator object. An iterable Generator object is a type of object that is used to iterate over a sequence of values. It works by creating an iterator object that generates a sequence of values. The generator object can then be used to loop over the sequence of values and perform operations on each using a for of
loop.
// Create a generator function
function* generatorFunction() {
yield 'first value';
yield 'second value';
yield 'third value';
}
// Create an iterable generator object
const iterableGenerator = generatorFunction();
// Iterate over the generator object
for (const value of iterableGenerator) {
console.log(value); // Outputs 'first value', 'second value' and 'third value'
}
Generators are an incredibly powerful feature of JavaScript and Typescript, and can greatly simplify the task of writing custom iterators. By utilizing generators, developers can create powerful Iterators easily.
Iterators
An Iterator in JavaScript is an object which permits us to traverse through a collection or list. Iterators outline the order and put into practice the iterator protocol which produces an object that is equipped with a next() method that supplies the subsequent item in the sequence, a value property and a done property.
The value property stores the current item in the iterator sequence, while the done property is a boolean that indicates if we are done consuming the values from the iterator, Iterators have a [Symbol.iterator] method, an iterators in TypeScript have the following interface:
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
interface IteratorResult<T> {
done: boolean;
value: T;
}
We can loop over an iterator by using a for of
of loop, this calls the next()
method on the iterator and pulls out the value stored on the value property until done
is false. An array is an example of an iterator, you can confirm this by calling arrray[Symbol.iterator]
on an array. All iterators have this [Symbol.iterator] property on them. Arrays are not the only iterators in Javascript. Sets, strings and maps are also iterators.
const arr = Array.from("Hello");
const iter = arr[Symbol.iterator]();
iter.next()
// { value: "H", done: true }
for(let v of iter) {
console.log(v)
}
// e
// l
// l
// o
Let's create a custom Iterator that generates the Fibonacci sequence for us.
function* fibb(v: number){
let i = 1;
let p = 2;
while (v < 20) {
let q = p + i;
p = i
i = q;
v++;
yield q;
}
}
const iter = fibb(5);
for (let v of iter) {
console.log(v)
}
We can also create an generator that traverses over a nested tree structure and returns each item in the list.
function* fibb(obj: Object, level: number): Generator<{ [x: number]: string}> {
const values = Object.values(obj);
for (let i= 0; i < values.length; i++){
if (typeof values[i] == "object") {
yield* fibb(values[i], level+1);
} else {
yield {[level]: values[i]}
}
}
}
const obj = {
name: "sam",
age: 20,
data: {
size: 10,
hair: "long",
color: "light"
},
friends: [
{
name: "Harry"
},
{
name: "Marcus"
},
{
name: "Lily"
}
]
}
const iter = fibb(obj, 1);
for (let i of iter){
console.log(i);
}
These two examples have so far demonstrated how we can use Generators to create powerful iterators. If it is not already apparent, a Generator function returns an iterator/iterable when it is invoked and we can simplify the process of creating custom iterators by using Generators. In our next post on Generators we would properly explore Generators in detail and how they could be used in our every day code.
To read more articles like this visit Netcreed