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 values 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 values 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 Promises to execute all promises in parallel, so it will not work with an asynchronous iterable).
-
AsyncGenerators combine function*/yield and async/await syntaxes:
const asyncGenerator = async function* (urls) {
for (const url of urls) {
const response = await fetch(url);
yield response;
}
};