You have used for...of on arrays, strings, Maps and Sets. Why do those work, but plain objects do not? The answer is the iteration protocol, a small agreement that any object can follow.
Two words
- An iterable is something you can loop over. It has a method named
[Symbol.iterator]()that returns an iterator. - An iterator is an object with a
next()method. Each call returns{ value, done }: the next value, and whether the sequence has finished.
const letters = ["a", "b", "c"];
const iterator = letters[Symbol.iterator](); // ask the array for an iterator
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 }
That is exactly what for...of does behind the scenes: get an iterator, call next() until done is true.
for (const letter of ["a", "b", "c"]) {
console.log(letter);
}
Built-in iterables
| Iterable | Gives |
|---|---|
| Array | items |
| String | characters |
| Map | [key, value] pairs |
| Set | values |
NodeList | elements |
arguments, typed arrays | items |
A plain object is not iterable, which is why for (const x of {a: 1}) throws TypeError: ... is not iterable.
Who else uses iterables?
Many features work with any iterable:
const set = new Set([1, 2, 2, 3]);
console.log([...set]); // spread → [1, 2, 3]
const [first, second] = set; // destructuring
console.log(first, second); // → 1 2
console.log(Array.from(set)); // Array.from → [1, 2, 3]
console.log(Math.max(...set)); // → 3
console.log(new Map([["a", 1]])); // Map constructor
Why this matters
- You can add your own loopable objects (Custom Iterables).
- Data can be produced lazily, one item at a time, without building a big array first (Generators).
- It explains error messages like “x is not iterable”, which usually mean you tried to loop or spread something that is not an array, such as
undefinedor an object.
try {
const [a] = undefined; // error on purpose
} catch (error) {
console.log(error.name); // → TypeError
}