An iterator is what lets for...of and the spread operator work on something — a protocol any object can implement. A generator is a special kind of function that builds an iterator automatically, and can pause its own execution.
Arrays, strings, Maps, and Sets are all iterable — they implement Symbol.iterator, a method that returns an object with a next() method producing one value at a time.
const numbers = [10, 20, 30]
const iterator = numbers[Symbol.iterator]()
iterator.next() // { value: 10, done: false }
iterator.next() // { value: 20, done: false }
iterator.next() // { value: 30, done: false }
iterator.next() // { value: undefined, done: true }for...of is really just repeatedly calling next() under the hood, stopping when done becomes true.
A generator, marked with function*, pauses at every yield and resumes exactly where it left off the next time it's called.
function* countUpTo(max) {
let count = 1
while (count <= max) {
yield count
count++
}
}
const counter = countUpTo(3)
counter.next() // { value: 1, done: false }
counter.next() // { value: 2, done: false }
counter.next() // { value: 3, done: false }
counter.next() // { value: undefined, done: true }Since a generator automatically implements the iterator protocol, it works directly with for...of and the spread operator.
for (const n of countUpTo(3)) {
console.log(n) // 1, 2, 3
}
[...countUpTo(3)] // [1, 2, 3]Because a generator only computes the next value when asked, it can represent a sequence too large — or infinite — to build all at once.
function* infiniteIds() {
let id = 1
while (true) {
yield id++
}
}
const ids = infiniteIds()
ids.next().value // 1
ids.next().value // 2
// never actually builds an infinite array — just produces one value at a timeThe connection to async/await
A generator's pause-and-resume behavior is also the mechanism async/await is built on top of, conceptually — both let a function stop mid-execution and continue later without blocking anything else.