for...of is not array syntax; it works on anything implementing the iterable protocol, which is one well-known method returning an object with a next().
class Paginator {
constructor(fetchPage) { this.fetchPage = fetchPage; }
*[Symbol.iterator]() {
let page = 1, batch;
while ((batch = this.fetchPage(page++)).length) {
yield* batch;
}
}
}
for (const row of new Paginator(fetchPage)) { /* ... */ }
A generator method is the short way to implement it — *[Symbol.iterator]() — and the consumer never learns that pages exist. Spread and destructuring use the same protocol, so an iterable also works with [...paginator], which will happily exhaust an infinite sequence. Objects are not iterable by default, which is why for...of over a plain object throws while for...in does not.