JavaScript Iterators
An iterator is an object that defines a next() method which returns objects with two properties: value (the next value) and done (a boolean indicating if the sequence is complete). Iterators power the for...of loop and other iteration constructs.
The Iterator Protocol
Any object that has a next() method returning { value, done } objects follows the iterator protocol. When done is true, the iteration is complete.
// Get iterator from an array
const arr = ["a", "b", "c"];
const iterator = arr[Symbol.iterator]();
// Call next() manually
console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { value: undefined, done: true }
// String iterator
const strIter = "Hi"[Symbol.iterator]();
console.log(strIter.next());
console.log(strIter.next());
console.log(strIter.next());Creating Custom Iterators
You can create your own iterators by defining an object with a next() method. To make an object iterable, add a [Symbol.iterator]() method that returns an iterator.
// Simple range iterator
function createRange(start, end) {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
const range = createRange(1, 5);
console.log(range.next());
console.log(range.next());
console.log(range.next());
console.log(range.next());
console.log(range.next());
console.log(range.next());Making Objects Iterable
To use for...of and spread on your own objects, implement the [Symbol.iterator]() method that returns an iterator.
// Make a range object iterable
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
};
// Now works with for...of
for (const num of range) {
console.log("Range:", num);
}
// And with spread
console.log("Spread:", [...range]);Infinite Iterators
Iterators don't have to end. You can create infinite sequences -- just be careful to use break or a limiting mechanism when consuming them.
// Infinite counter
const counter = {
[Symbol.iterator]() {
let n = 0;
return {
next() {
return { value: n++, done: false };
}
};
}
};
// Take first 5 values
const first5 = [];
for (const n of counter) {
if (n >= 5) break;
first5.push(n);
}
console.log("First 5:", first5);
// Fibonacci iterator
function fibonacci() {
let a = 0, b = 1;
return {
[Symbol.iterator]() { return this; },
next() {
const value = a;
[a, b] = [b, a + b];
return { value, done: false };
}
};
}
const fib = fibonacci();
const first10 = [];
for (const n of fib) {
if (n > 50) break;
first10.push(n);
}
console.log("Fibonacci:", first10);Iterator Helpers
Understanding the iterator pattern helps you work with built-in iterables more effectively and create reusable utility functions.
// Take: get first n items from any iterable
function take(iterable, count) {
const result = [];
for (const item of iterable) {
result.push(item);
if (result.length >= count) break;
}
return result;
}
console.log("Take 3 from array:", take([10, 20, 30, 40, 50], 3));
console.log("Take 4 from string:", take("JavaScript", 4));
// Zip: combine two iterables
function* zip(a, b) {
const iterA = a[Symbol.iterator]();
const iterB = b[Symbol.iterator]();
while (true) {
const nextA = iterA.next();
const nextB = iterB.next();
if (nextA.done || nextB.done) break;
yield [nextA.value, nextB.value];
}
}
console.log("Zip:", [...zip([1, 2, 3], ["a", "b", "c"])]);| Concept | Description |
|---|---|
| Iterator | Object with next() returning {value, done} |
| Iterable | Object with [Symbol.iterator]() returning iterator |
| next() | Returns {value: any, done: boolean} |
| done: false | More values available |
| done: true | Iteration complete |
| Symbol.iterator | Well-known symbol for the iterator method |