Iteration in JavaScript is a pair of protocols that any object can implement (ECMA-262,
Iteration). for…of, spread, destructuring and many built-ins consume them. A
generator is a function that produces an iterator.
The iteration protocols
| Protocol | Requires |
|---|---|
| Iterable | A method named [Symbol.iterator] that returns an iterator |
| Iterator | A next() method returning an object with value and done |
An iterator may also have return(value) and
throw(error) methods. return is what a consumer calls when it
stops early, so an iterator can release resources.
const range = {
from: 1,
to: 3,
[Symbol.iterator]() {
let n = this.from;
const to = this.to;
return {
next: () => n <= to ? { value: n++, done: false } : { value: undefined, done: true },
return: () => ({ value: undefined, done: true }),
};
},
};
[...range]; // [1, 2, 3]
What uses them
| Consumer | Source |
|---|---|
for…of, [...x], f(...x) | Any iterable |
| Array destructuring | Any iterable; it stops early and calls return |
Array.from, new Map, new Set, Object.fromEntries | Any iterable |
Promise.all and the other combinators | Any iterable |
yield* | Any iterable |
Arrays, strings, maps, sets, typed arrays and arguments are built-in
iterables. Plain objects are not; use Object.entries(obj). If a
consumer leaves for…of early, through break,
return or a throw, it calls the iterator’s return.
The Iterator object and helpers
ES2025 added a global Iterator and helper methods. The built-in iterators
inherit from Iterator.prototype, and helpers are lazy: they run only
as values are pulled.
| Method | Returns | Since |
|---|---|---|
Iterator.from(x) | Wraps an iterable or iterator-like object | ES2025 |
map, filter, take, drop, flatMap | New iterators | ES2025 |
reduce, toArray, forEach, some, every, find | Values, consuming the iterator | ES2025 |
Iterator.concat(...iterables) | The values of each iterable in turn | ES2026 |
Iterator.zip, Iterator.zipKeyed | Tuples or records drawn from several iterables | Draft |
chunks, windows, includes, join | Grouped values, a membership test and a joined string | Draft |
function* naturals() { let n = 1; while (true) yield n++; }
naturals().filter(n => n % 2).map(n => n * n).take(3).toArray();
// [1, 9, 25]
Iterators are single-use. Once one is exhausted, calling next keeps
returning done: true. Iterator cannot be called
directly, only subclassed.
Generators
GeneratorDeclaration:
function * BindingIdentifier ( FormalParameters ) { GeneratorBody }
GeneratorExpression:
function * [BindingIdentifier] ( FormalParameters ) { GeneratorBody }
GeneratorMethod:
* ClassElementName ( UniqueFormalParameters ) { GeneratorBody }
YieldExpression:
yield
yield [no LineTerminator here] AssignmentExpression
yield [no LineTerminator here] * AssignmentExpression
Calling a generator function (ES2015) runs none of its body. It returns a generator
object that is both an iterator and an iterable. Each next() runs the body
up to the next yield.
function* ask() {
const a = yield 'first';
const b = yield 'second';
return a + b;
}
const g = ask();
g.next(); // { value: 'first', done: false }
g.next(1); // { value: 'second', done: false } (a = 1)
g.next(2); // { value: 3, done: true } (b = 2)
g.next(); // { value: undefined, done: true }
| Method | Effect |
|---|---|
g.next(v) | Resumes; v becomes the value of the paused yield. The argument of the first next is lost |
g.return(v) | Resumes as if a return stood at the yield, running any finally |
g.throw(e) | Resumes by throwing e at the yield |
- A generator is in one of four states: suspended at the start, suspended at a yield, executing, or completed.
- Re-entering a running generator throws a
TypeError. - A
returnvalue appears only as the finalvaluewithdone: true, sofor…ofand spread ignore it. - A generator function is not a constructor, but it has a
prototypeproperty, from which its generator objects inherit.
yield*
yield* iterable delegates to another iterable: it passes
next, throw and return calls through. Its
value is the delegate’s return value.
function* inner() { yield 1; return 2; }
function* outer() {
const result = yield* inner();
yield result;
}
[...outer()]; // [1, 2]
Async iteration
| Protocol | Requires |
|---|---|
| Async iterable | A method named [Symbol.asyncIterator] that returns an async iterator |
| Async iterator | next() returns a promise for { value, done } |
for await…of (ES2018) works in async functions and at the top level of
modules. Given a synchronous iterable, it falls back to
Symbol.iterator and awaits each value.
async function total(stream) {
let sum = 0;
for await (const chunk of stream) sum += chunk.length;
return sum;
}
Async generators
async function* (ES2018) may use both await and
yield. Its methods return promises, and calls made while it runs are
queued. A yielded promise is awaited first, so an async generator never yields a
promise. yield* delegates to an async iterable, or to a sync one if
that is all there is.
async function* poll(url) {
while (true) {
const res = await fetch(url);
yield await res.json();
await new Promise(r => setTimeout(r, 1000));
}
}
Promises and the event loop are on the
Async page. Reading an array from an async source is
Array.fromAsync (ES2026).