What makes JavaScript Iterables Iterable?
Introduction
Have you wondered why arrays can be iterated (for...of) but objects cannot? JavaScript Iterables rely on the Iterator Protocol to implement iterable data structures.
As long as a data structure implements this protocol, it can become an Iterable, manipulable by for...of, spread operators, Array.from(), or destructuring assignment syntax.
The concept based on the Iterator Protocol extends to knowledge related to Generator Functions, enabling the creation of more specialized data structures.
Iterables
The so-called “iterable” refers to objects that implement the built-in special property Symbol.iterator and return an “iterator”.
For example, arrays are inherently iterable:
const arr = [1, 2, 3];console.log(typeof arr[Symbol.iterator]); // 'function'Iterator
An Iterator must proceed to the next iteration through next, and each call of next() returns an object { value, done }, representing the current value and whether it’s finished:
const iterator = arr[Symbol.iterator]();console.log(iterator.next()); // { value: 1, done: false }console.log(iterator.next()); // { value: 2, done: false }console.log(iterator.next()); // { value: 3, done: false }console.log(iterator.next()); // { value: undefined, done: true }Example
You can define an iterable object yourself, such as a “range generator”:
const range = { from: 1, to: 3, [Symbol.iterator]() { let current = this.from; const end = this.to; return { next() { if (current <= end) { return { value: current++, done: false }; } else { return { done: true }; } }, }; },};
for (const num of range) { console.log(num); // 1, 2, 3}Generator: A More Concise Way to Implement an Iterator
While the above range works, it looks a bit cumbersome.
Generator functions provide a more intuitive way to create iterators.
function* range(from, to) { for (let i = from; i <= to; i++) { yield i; }}
for (const num of range(1, 3)) { console.log(num); // 1, 2, 3}Generators are a special type of function that do not execute their internal code immediately; they can “pause” and “resume” during execution. Each time yield is executed, it returns a result structured as { value, done }, rewriting the previous example:
function* threeStepGenerator() { yield 1; yield 2; yield 3; console.log('End');}
const gen = threeStepGenerator();console.log('Generator created');
console.log(gen.next()); // { value: 1, done: false }console.log(gen.next()); // { value: 2, done: false }console.log(gen.next()); // { value: 3, done: false }console.log(gen.next()); // { value: undefined, done: true }Summary
- Iterable: An object that has a
Symbol.iteratormethod. - Symbol.iterator: An object that has a
next()method and can gradually return{ value, done }. - Generator: Defined with
function*, usingyieldinternally to produce values, essentially automating the creation of an Iterator.
In practice, concepts of lazy evaluation, asynchronous control, data stream generation, coroutine simulation, and iterator encapsulation can all apply relevantly.
Further Reading
- JavaScript Iterators Explained with Examples | Level up Your JS - Ijemma Onwuzulike
- JavaScript Generators in 7 Minutes: What They Are and How They’re the Overpowered Iterator - Ijemma Onwuzulike
- Iterables - javascript.info
- Generators - javascript.info
- [Day 26] Lazy Evaluation and Generator Function - Monica