Welcome to a journey through the fascinating world of iterators, iterables, and generators in JavaScript! These concepts are usually shrouded in mystery but fear not - by the end of this article, you'll have a clear understanding of how they work and how they can empower you to write more concise and efficient code. So, let's dive in and demystify these essential elements of JavaScript programming.
What is an iterator?
The definition of an iterator can be summarized using the following interface:
interface MyIterationResult<T> {
value?: T;
done: boolean;
}
interface MyIterator<T> {
next(): IterationResult<T>;
}
So basically, an iterator is an object with a method next
that returns the result of the current iteration upon each invocation.
An example iterator:
/**
* @returns {MyIterator<number>} an iterator that iterates from `start` to `end`
*/
function getRangeIterator(start: number, end: number): MyIterator<number> {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
} else return { done: true };
},
};
}
const range = getRangeIterator(1, 5);
let { value, done } = range.next();
while (!done) {
console.log(value); // 1, 2, 3, 4, 5
({ value, done } = range.next());
}
What is an iterable?
interface MyIterable<T> {
[Symbol.iterator](): MyIterator<T>;
}
Any object that has a method defined for the system symbol Symbol.iterator
that returns an iterator, is considered an iterable.
An example iterable:
class MyRange implements MyIterable<number> {
start: number;
end: number;
constructor(start: number, end: number) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
return getRangeIterator(this.start, this.end);
}
}
You can loop through an iterable using the for..of
construct:
const myRange = new MyRange(1, 5);
for (const value of myRange) {
console.log(value); // 1, 2, 3, 4, 5
}
You can also use the ...
spread syntax with an iterable to convert it to an array of corresponding values:
const myRangeArray = [...myRange];
console.log(myRangeArray); // (5) [1, 2, 3, 4, 5]
What are generator functions?
Generator functions are a special type of function that allows you to define an iterative algorithm by writing a single function that can maintain its state. Unlike regular functions that execute and return a single value, generator functions can pause their execution and yield control back to the caller while retaining their context.
function* myGenerator() {
yield 1;
yield 2;
}
In the example above, myGenerator
is a generator function declared using the function*
syntax. Within the function body, yield
is used to pause execution and produce a value. Each time yield
is encountered, the function pauses, allowing the caller to retrieve the yielded value.
const gen = myGenerator();
console.log(gen.next().value); // Output: 1
console.log(gen.next().value); // Output: 2
console.log(gen); // Generator {}
Generator functions return an object that is an instance of the class Generator
. Interestingly, generators are both iterators and iterables.
const genArray = [...gen];
console.log(genArray); // (2) [1, 2]
This proves invaluable when constructing an iterable, as it allows us to substitute a generator function for the more verbose iterator.
Here is an example of MyRange
class from a previous example, re-implemented using generator functions:
class MyRange {
start: number;
end: number;
constructor(start: number, end: number) {
this.start = start;
this.end = end;
}
*[Symbol.iterator]() {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
const myRange = new MyRange(1, 5);
for (const value of myRange) {
console.log(value); // 1, 2, 3, 4, 5
}
Generators are particularly useful for dealing with asynchronous code, iteration over large datasets, and implementing custom iteration behaviors. They offer a powerful mechanism for writing clean, readable, and efficient code by enabling lazy evaluation and on-demand generation of values.
In closing, I hope this article provides a concise overview of iterables and generators, equipping you with the knowledge to leverage these powerful features effectively in your projects.