TypeScript Iterators and Generators
Iterators and generators are important patterns in JavaScript/TypeScript for working with collections.
They provide a unified way to iterate over data, making it easier to handle large data streams, infinite sequences, and more.
SVG Diagram: Iterators and Generators
Why Do We Need Iterators and Generators
When working with collection data, we often need to iterate over data structures such as arrays and objects.
Iterators provide a unified, customizable iteration interface, allowing any object to be iterated over.
Generators are a concise way to create iterators. They allow you to pause and resume execution using functions, making them ideal for handling large data streams or infinite sequences.
Concept explanation:An iterator is an object that provides a next() method for traversing data. A generator is a special function that can pause during execution and return a value.
Iterable Protocol
Objects that implement the Symbol.iterator method can be iterated over with a for...of loop.
Example
var arr = [1, 2, 3];
for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
var item = arr_1[_i];
console.log("Array elements: " + item);
}
// Strings are iterable by default
var str = "hello";
for (var _i = 0, str_1 = str; _i < str_1.length; _i++) {
var char = str_1[_i];
console.log("Characters: " + char);
}
Output:
数组元素: 1 数组元素: 2 数组元素: 3 字符: h 字符: e 字符: l 字符: l 字符: o
Explanation:Arrays and strings both have built-in Symbol.iterator implementations, so they can be iterated over directly with for...of.
Custom Iterable Objects
Make ordinary objects implement the Symbol.iterator interface so they can be iterated over.
Example
var range = {
from: 1,
to: 5,
// Implement the Symbol.iterator method
[Symbol.iterator]: function() {
return {
current: this.from,
last: this.to,
// The next method returns the iteration result
next: function() {
if (this.current <= this.last) {
// Not done, return the current value and increment
return { done: false, value: this.current++ };
}
// Done
return { done: true, value: undefined };
}
};
}
};
// Iterate with for...of
for (var _i = 0, range_1 = range; _i < range_1.length; _i++) {
var num = range_1[_i];
console.log("Range: " + num);
}
Iterator Protocol:An iterator must have a next() method that returns an object in the format { done: boolean, value: any }.
Generator Functions
Use the function* syntax to create generators, and use yield to pause execution and return a value.
Example
function* numberGenerator() {
yield 1; // Pause and return 1
yield 2; // Pause and return 2
yield 3; // Pause and return 3
}
// Create a generator instance
var gen = numberGenerator();
// Each call to next() executes until the next yield
console.log("First: " + gen.next().value);
console.log("Second: " + gen.next().value);
console.log("Third: " + gen.next().value);
console.log("Done: " + gen.next().done);
Output:
第一个: 1 第二个: 2 第三个: 3 完成: true
Generator:A generator function returns an iterator, and each call to next() executes until the next yield statement.
Infinite Generators
Generators can produce infinite sequences. Since they use lazy evaluation, they do not consume infinite memory.
Example
// Each call generates only one number, not all numbers at once
function* infiniteNumbers() {
var n = 1;
while (true) { // Infinite loop
yield n++; // Pause and return the current value, then increment
}
}
var gen = infiniteNumbers();
console.log("1st: " + gen.next().value);
console.log("2nd: " + gen.next().value);
console.log("3rd: " + gen.next().value);
// Get only the first 5 numbers
var nums = [];
var iter = infiniteNumbers();
for (var i = 0; i < 5; i++) {
nums.push(iter.next().value);
}
console.log("First 5: " + nums);
Output:
第1个: 1 第2个: 2 第3个: 3 前5个: 1,2,3,4,5
Lazy evaluation:The biggest advantage of generators is lazy evaluation: the next value is only computed when next() is called, making them ideal for handling infinite sequences.
Delegated Generators
Use yield* to delegate to another generator or iterable object.
Example
function* gen1() {
yield 1;
yield 2;
}
// Second generator
function* gen2() {
yield 3;
yield 4;
}
// Combined generator: uses yield* delegation
function* combined() {
yield* gen1(); // Delegate to gen1
yield* gen2(); // Delegate to gen2
}
// Iterate over the combined generator
for (var _i = 0, combined_1 = combined(); _i < combined_1.length; _i++) {
var num = combined_1[_i];
console.log("Value: " + num);
}
Output:
值: 1 值: 2 值: 3 值: 4
yield*:Delegated generators can combine multiple generators or iterable objects, making them well-suited for building reusable data streams.
TypeScript Generator Types
Generator type annotations use the Generator type.
Example
function* idGenerator(): Generator<number, void, unknown> {
var i = 1;
while (i <= 3) {
yield i++; // yield number type
}
// return void
}
var gen = idGenerator();
console.log(Array.from(gen));
Type explanation:Generator<T, R, N> means: T is the type of yield, R is the final return type, and N is the type of the next() parameter.
Notes
- Iterator protocol:Implement Symbol.iterator to return an object with a next() method
- Generator syntax:Use function* instead of function
- yield keyword:Pauses execution and returns a value
- Lazy evaluation:Generators compute on demand and do not generate all values at once
Best practices:Use generators when handling large data streams, infinite sequences, or scenarios that require pause/resume.
Summary
Iterators and generators are powerful data processing tools in TypeScript.
- Iterable objects:Implement the Symbol.iterator interface
- Generators:Created with function* and yield
- yield:Pause execution and return values
- Delegation:Use yield* to combine multiple generators
Other ExtensionsRecommendation:Use iterators and generators when you need to iterate over custom objects, process data streams, or create infinite sequences.