Generators and Iterators in JavaScript
In JavaScript, iterators and generators are powerful tools that allow you to work with data in a more efficient and controlled way, especially when dealing with large datasets or complex iterations.
Generators
- A JavaScript generator function is a special function that can pause and resume execution.
- They are created using the
function*
syntax and can be paused and resumed at any point during their execution using theyield
keyword. - When a generator function is called, it returns a generator object, an iterator.
Use cases:
- Generators can handle large datasets without loading the entire set into memory.
- They allow more complex iteration patterns than traditional loops or iterable constructs.
- With the advent of
async/await
, built on generators, they can simplify asynchronous code by making it look more like synchronous code.
Basic Example
// Define a generator function
function* generatorExample() {
yield 1; // Pauses and returns 1
yield 2; // Pauses and returns 2
yield 3; // Pauses and returns 3
}
// Create a generator object
const gen = generatorExample();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().value); // undefined
Infinite Generator Example
You can create a generator that never stops producing values:
function* infiniteGenerator() {
let index = 0;
while (true) {
yield index++;
}
}
const infGen = infiniteGenerator();
console.log(infGen.next().value); // 0
console.log(infGen.next().value); // 1
console.log(infGen.next().value); // 2
console.log(infGen.next().value); // 3
Generator with async/await
async function* asyncGenerator() {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
for (const url of urls) {
const response = await fetch(url); // Await the async fetch call
const data = await response.json(); // Await the conversion to JSON
yield data; // Yield the data one at a time
}
}
(async () => {
const gen = asyncGenerator();
for await (const data of gen) {
console.log(data); // Logs each data object as it's received
}
})();
Iterators and Iterables
An iterator is an object that provides a way to access elements in a collection one at a time.
Think of it like a pointer that moves through the collection, giving you the current element each time you request it.
Built-in iterators exist for arrays, strings, maps, sets, and other iterable objects.
You can use the for...of
loop to easily iterate over iterables.
Here is an analogy: Imagine iterating over a playlist. An iterator would be like a song player that goes to the next song each time you press a button.
Example
const numbers = [1, 2, 3, 4, 5];
const numberIterator = numbers[Symbol.iterator]();
console.log(numberIterator.next()); // { value: 1, done: false }
console.log(numberIterator.next()); // { value: 2, done: false }
console.log(numberIterator.next()); // { value: 3, done: false }
Create a Custom Iterator
- To create a custom iterator, you need to define an object with a
next()
method that return an object object with two properties:value
anddone
. - The
value
property holds the current item in the collection, and thedone
property is a boolean indicating whether the iteration has reached the end of the collection.
function createCustomIterator(array) {
let index = 0;
return {
next: function() {
if (index < array.length) {
const value = array[index] + 1; // Increment each number by 1
index++;
return { value, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
// Use the custom iterator
const myArray = [1, 2, 3, 4, 5];
const iterator = createCustomIterator(myArray);
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
Create Iterable Protocol
- To make an object
iterable
using thefor...of
loop, you need to implement theiterable
protocol by defining a[Symbol.iterator]
method.
const customIterable = {
array: [1, 2, 3, 4, 5],
[Symbol.iterator]: function() {
let index = 0;
let array = this.array;
return {
next: function() {
if (index < array.length) {
const value = array[index] + 1;
index++;
return { value, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
// Use the custom iterable with a for...of loop
for (let value of customIterable) {
console.log(value);
}
Summary
- In JavaScript, both generators and iterators handle sequences of values, but they have different purposes and functionalities.
Generator
is a function that produces or yields a sequence of values using theyield
statement. You can think of it as a function that can be paused and resumed.Iterator
is an object that provides a mechanism for traversing a collection of data, one element at a time.Iterable
is an object that has[Symbol.iterator]
method. This method must return an iterator object.