There are three types you should learn about
You can create them without any function*
syntax. I use some simplified python range
function as an example, a sequence starting at start
and ending at stop - 1
:
Definition: An Iterator
must have a next
method/property that returns {done: boolean, value?: any}
.
Intrinsically an Iterator
must maintain some kind of state.
const iterator = (start, stop) => {
let current = start;
return {
next: () => {
if (current >= stop) return {done: true};
return {done: false, value: current++};
}
};
};
You can consume an instantiated Iterator
it = iterator(start, stop)
by repeatedly calling it.next();
. The done
key tells you whether the iterator is “done”: if the done
key is true
, the value
has no meaning and further calls to next
will the done
key set to true
. If the done
key is false
, then value
has meaning.
Definition: An Iterable
must define a [Symbol.iterator]
method/property that returns an Iterator
.
const iterable = (start, stop) => ({
[Symbol.iterator]: () => iterator(start, stop)
});
You can consume an Iterable
either by calling its [Symbol.iterator]
method/property to get an Iterator
that you consume as above, or by using the for (const value of iterable(start, stop))
syntax that iterates over the value
s yielded by the iterable’s iterator, and breaks as soon as done
is true. You can also use Array.from
to create an Array
from a (finite) iterable.
Definition: A Generator
is both an Iterable
and an Iterator
.
It’s more idiomatic to use prototype
or a class
but it also works with an object
literal:
const generator = (start, stop) => {
let current = start;
const self = {
[Symbol.iterator]: () => self,
next: () => {
if (current >= stop) return {done: true};
return {done: false, value: current++};
}
};
return self;
};
Generator
also gives back meaning to value
even when done
is true
with the return
method/property. It also defines a throw
method/property. I would consider this advanced usage. I have never used it myself.
The function*
syntax creates a GeneratorFunction
that returns a Generator
. The value
s yielded are the ones you yield with the yield
and yield*
syntax.
Iterative:
const generator = function* (start, stop) {
let current = start;
while (current < stop) yield current++;
};
Recursive:
const generator = function* (start, stop) {
if (start < stop) {
yield start;
yield* generator(start+1, stop);
}
};
Some remarks:
For some reason, TypeScript defines IterableIterator
~instead of~ in addition to Generator
, ~merges~ also has the additional return
and throw
Generator
methods/properties as optional into Iterator
, and forces the method/property [Symbol.iterator]
of IterableIterator
to return an IterableIterator
, and the method/property [Symbol.iterator]
of Generator
to return a Generator
.
You can also use class
or prototype
syntax to create iterables and iterators, for instance:
class MyIterator {
constructor (start, stop) {
this.current = start;
this.stop = stop;
}
next() {
if (this.current >= this.stop) return {done: true};
return {done: false, value: this.current++};
}
}
or
function MyIterator (start, stop) {
this.current = start;
this.stop = stop;
}
MyIterator.prototype.next = function {
if (this.current >= this.stop) return {done: true};
return {done: false, value: this.current++};
};
From there you can easily derive asynchronous iterators/iterables/generators:
- The
next
method/property of an AsyncIterator
must return a Promise<{done: boolean, value?: any}>
-
[Symbol.iterator]
of Iterable
is replaced by [Symbol.asyncIterator]
of AsyncIterable
and returns an AsyncIterator
-
for (const ... of someIterable)
syntax is replaced by for await (const ... of someAsyncIterable)
- Unfortunately, you have to roll out your own version of the asynchronous version of
Array.from
as it does not exist yet (and Promise.all
is a false friend: it needs a synchronous Iterable
of Promise
s to execute all promises in parallel, so it will not work with an asynchronous iterable).
-
AsyncGenerator
s combine function*
/yield
and async
/await
syntaxes:
const asyncGenerator = async function* (urls) {
for (const url of urls) {
const response = await fetch(url);
yield response;
}
};