Replace 2022-12-15: New part “How will this proposal have an effect on future JavaScript APIs?”
On this weblog put up, we have a look at the ECMAScript proposal “Iterator helpers” by Gus Caplan, Michael Ficarra, Adam Vandolder, Jason Orendorff, Kevin Gibbons, and Yulia Startsev. It introduces utility strategies for working with iterable information: .map()
, .filter()
, .take()
, and so forth.
The fashion of the proposed API clashes with the fashion of the present iteration API. We’ll discover how we will repair that.
Synchronous versus asynchronous iteration
JavaScript helps two sorts of iteration modes:
We’ll first discover synchronous iteration in depth after which briefly have a look at asynchronous iteration – the asynchronous a part of the proposal is similar to the synchronous half.
Synchronous iteration
Iteration: a protocol for sequential information customers
In JavaScript, there are a number of constructs that eat information sequentially – one worth at a time – for instance, the for-of
loop and spreading into Arrays.
A protocol consists of interfaces and guidelines for utilizing them.
The iteration protocol is utilized by JavaScript’s sequential information customers to entry their enter. Any information construction that implements this protocol can subsequently be consumed by them.
The interfaces of the iteration protocol
The next roles are concerned within the synchronous iteration protocol:
-
Values that assist the iteration protocol are known as iterables. They return iterators by way of a technique
[Symbol.iterator]()
. -
We get the iterated values by repeatedly invoking technique
.subsequent()
of the iterator returned by the iterable.
These are the TypeScript sorts for these roles:
interface Iterable<T> {
[Symbol.iterator]() : Iterator<T>;
}
interface Iterator<T> {
subsequent() : IteratorResult<T>;
}
interface IteratorResult<T> {
performed: boolean;
worth?: T;
}
The Iterator
technique .subsequent()
returns:
- An object
{performed: false, worth: x}
for every iterated worthx
. - The item
{performed: true}
after the final iterated worth.
In different phrases: We name .subsequent()
till it returns an object whose property .performed
is true
.
For example, let’s use the iteration protocol to entry the weather of a Set:
const iterable = new Set(['hello', 'world']);
const iterator = iterable[Symbol.iterator]();
assert.deepEqual(
iterator.subsequent(),
{ worth: 'hiya', performed: false }
);
assert.deepEqual(
iterator.subsequent(),
{ worth: 'world', performed: false }
);
assert.deepEqual(
iterator.subsequent(),
{ worth: undefined, performed: true }
);
Since Units are iterable, we will use them with iteration-based information customers corresponding to spreading into Arrays (line A) and for-of
loops (line B):
const iterable = new Set(['hello', 'world']);
assert.deepEqual(
[...iterable],
['hello', 'world']
);
for (const x of iterable) {
console.log(x);
}
Observe that we by no means noticed iterators – these are solely used internally by the customers.
The JavaScript commonplace library has extra iteration-based information producers and customers. We’ll have a look at these subsequent.
Iteration-based information producers
These information buildings are iterable:
The next information buildings have the strategies .keys()
, .values()
, and .entries()
that return iterables that aren’t Arrays:
Synchronous generator capabilities and strategies expose their yielded values by way of iterable objects that they return:
operate* createSyncIterable() {
yield 'a';
yield 'b';
yield 'c';
}
We’ll use the results of createSyncIterable()
to show iteration-based information customers within the subsequent subsection.
Iteration-based information customers
The entire following constructs entry their enter by way of the iteration protocol.
The for-of
loop:
for (const x of createSyncIterable()) {
console.log(x);
}
Spreading:
assert.deepEqual(
['>', ...createSyncIterable(), '<'],
['>', 'a', 'b', 'c', '<']
);
const arr = [];
arr.push('>', ...createSyncIterable(), '<');
assert.deepEqual(
arr,
['>', 'a', 'b', 'c', '<']
);
Array.from()
:
assert.deepEqual(
Array.from(createSyncIterable()),
['a', 'b', 'c']
);
assert.deepEqual(
Array.from(createSyncIterable(), s => s + s),
['aa', 'bb', 'cc']
);
Array-destructuring:
const [elem0, elem1] = createSyncIterable();
assert.equal(elem0, 'a');
assert.equal(elem1, 'b');
Processing iterables by way of mills
Mills produce iterables, however they’ll additionally eat them. That makes them a flexible instrument for remodeling iterables:
operate* map(iterable, callback) {
for (const x of iterable) {
yield callback(x);
}
}
assert.deepEqual(
Array.from(
map([1, 2, 3, 4], x => x ** 2)
),
[1, 4, 9, 16]
);
operate* filter(iterable, callback) {
for (const x of iterable) {
if (callback(x)) {
yield x;
}
}
}
assert.deepEqual(
Array.from(
filter([1, 2, 3, 4], x => (x%2) === 0
)),
[2, 4]
);
The inheritance of the present iteration API
The entire iterators created by JavaScript’s commonplace library have a standard prototype which the ECMAScript specification calls %IteratorPrototype%
.
Array iterators
We create an Array iterator like this:
const arrayIterator = [][Symbol.iterator]();
This object has a prototype with two properties. Let’s name it ArrayIteratorPrototype
:
const ArrayIteratorPrototype = Object.getPrototypeOf(arrayIterator);
assert.deepEqual(
Replicate.ownKeys(ArrayIteratorPrototype),
[ 'next', Symbol.toStringTag ]
);
assert.equal(
ArrayIteratorPrototype[Symbol.toStringTag],
'Array Iterator'
);
The prototype of ArrayIteratorPrototype
is %IteratorPrototype%
. This object has a technique whose key’s Image.iterator
. Due to this fact, all built-in iterators are iterable.
const IteratorPrototype = Object.getPrototypeOf(ArrayIteratorPrototype);
assert.deepEqual(
Replicate.ownKeys(IteratorPrototype),
[ Symbol.iterator ]
);
The prototype of IteratorPrototype
is Object.prototype
.
assert.equal(
Object.getPrototypeOf(IteratorPrototype) === Object.prototype,
true
);
It is a diagram for this chain of prototypes:
Generator objects
Roughly, a generator object is an iterator for the values yielded by a generator operate genFunc()
. We create it by calling genFunc()
:
operate* genFunc() {}
const genObj = genFunc();
The prototype of genObj
is genFunc.prototype
:
assert.equal(
Object.getPrototypeOf(genObj) === genFunc.prototype,
true
);
assert.deepEqual(
Replicate.ownKeys(genFunc.prototype),
[]
);
The prototype of genFunc.prototype
is an object that’s shared with all generator objects. Along with the iterator technique .subsequent()
, it has generator-specific strategies corresponding to .return()
and .throw()
. The ECMAScript specification calls it %GeneratorFunction.prototype.prototype%
:
const GeneratorFunction_prototype_prototype =
Object.getPrototypeOf(genFunc.prototype);
assert.deepEqual(
Replicate.ownKeys(GeneratorFunction_prototype_prototype),
[
'constructor',
'next',
'return',
'throw',
Symbol.toStringTag,
]
);
assert.equal(
GeneratorFunction_prototype_prototype[Symbol.toStringTag],
'Generator'
);
The prototype of %GeneratorFunction.prototype.prototype%
is %IteratorPrototype%
:
const p = Object.getPrototypeOf;
const IteratorPrototype = p(p([][Symbol.iterator]()));
assert.equal(
Object.getPrototypeOf(GeneratorFunction_prototype_prototype),
IteratorPrototype
);
Why are the built-in iterators iterable?
As we have now seen, generator objects are, at their cores, iterators (they’ve a technique .subsequent()
), not iterables. Nevertheless, we’d additionally like to make use of mills to implement iterables. That’s why generator objects have a technique [Symbol.iterator]()
that returns this
. They inherit this technique from %IteratorPrototype%
.
The next code demonstrates that every generator object returns itself when it’s requested for an iterator:
operate* gen() {}
const genObj = gen();
assert.equal(
genObj[Symbol.iterator](),
genObj
);
Iteration quirk: two sorts of iterables
Alas, iterable iterators imply that there are two sorts of iterables:
-
Iterable iterators are one-time iterables: They at all times return the identical iterator when
[Symbol.iterator]()
known as (iteration continues). -
Arrays, Units, and so forth. are many-times iterables: They at all times return contemporary iterators (iteration restarts).
const iterOnce = ['a', 'b', 'c'].values();
assert.deepEqual(
[...iterOnce, ...iterOnce, ...iterOnce],
['a', 'b', 'c']
);
const iterMany = ['a', 'b', 'c'];
assert.deepEqual(
[...iterMany, ...iterMany, ...iterMany],
['a','b','c', 'a','b','c', 'a','b','c']
);
The brand new API: synchronous iteration
We now have already seen that %IteratorPrototype%
is the prototype of all built-in iterators. The proposal introduces a category Iterator
:
Iterator.from()
is a utility technique that we’ll discover quickly.Iterator.prototype
refers to%IteratorPrototype%
.%IteratorPrototype%.constructor
refers toIterator
.Iterator.prototype
accommodates numerous strategies which are inherited by iterators – for instance:Iterator.prototype.map(mapFn)
returns a mapped model ofthis
Iterator.prototype.take(restrict)
returns an iterator for the primaryrestrict
values ofthis
.Iterator.prototype.toArray()
returns an Array with the values ofthis
.
Iterator.from()
: creating API iterators
The static technique Iterator.from(x)
returns an instanceof Iterator
:
- If
x
is a synchronous API iterable, it returnsx[Symbol.iterator]()
. - If
x
is a synchronous API iterator, it returnsx
unchanged. - If
x
is a synchronous legacy iterator (that doesn’t assist the brand new API), it wraps it in order that it helps the brand new API and returns the consequence. - If
x
is a synchronous legacy iterable, it wraps the results ofx[Symbol.iterator]()
and returns it.
Within the following instance, we use Iterator.from()
to transform a legacy iterator to an API iterator:
const legacyIterator = {
subsequent() {
return { performed: false, worth: '#' };
}
};
assert.equal(
Iterator.from(legacyIterator) instanceof Iterator,
true
);
assert.deepEqual(
Iterator.from(legacyIterator).take(3).toArray(),
['#', '#', '#']
);
An summary of the brand new Iterator.prototype
strategies
The next subsections give an summary of the brand new Iterator.prototype
strategies. They may use this operate to create a synchronous iterable:
operate* createSyncIterator() {
yield 'a'; yield 'b'; yield 'c'; yield 'd';
}
A number of the iterator strategies hold a counter for the iterated values and cross it on to their callbacks:
.each()
.filter()
.discover()
.flatMap()
.forEach()
.map()
.scale back()
.some()
Iterator strategies that return iterators
iterator.take(restrict)
This technique returns an iterator with the primary restrict
values of iterator
.
Sort signature:
Iterator<T>.prototype.take(restrict: quantity): Iterator<T>
Instance:
assert.deepEqual(
createSyncIterator().take(1).toArray(),
['a']
);
iterator.drop(restrict)
This technique returns an iterator that with all values of iterator
, aside from the primary restrict
ones. That’s, iteration begins when the iteration counter is restrict
.
Sort signature:
Iterator<T>.prototype.drop(restrict: quantity): Iterator<T>
Instance:
assert.deepEqual(
createSyncIterator().drop(1).toArray(),
['b', 'c', 'd']
);
iterator.filter(filterFn)
This technique returns an iterator whose values are the values of iterator
for which filterFn
returns true
.
Sort signature:
Iterator<T>.prototype.filter(
filterFn: (worth: T, counter: quantity) => boolean
): Iterator<T>
Instance:
assert.deepEqual(
createSyncIterator().filter(x => x <= 'b').toArray(),
['a', 'b']
);
iterator.map(mapFn)
This technique returns an iterator whose values are the results of making use of mapFn
to the values of iterator
.
Sort signature:
Iterator<T>.prototype.map<U>(
mapFn: (worth: T, counter: quantity) => U
): Iterator<U>
Instance:
assert.deepEqual(
createSyncIterator().map(x => x + x).toArray(),
['aa', 'bb', 'cc', 'dd']
);
iterator.flatMap(mapFn)
This technique returns an iterator whose values are the values of the iterables or iterators which are the outcomes of making use of mapFn
to the values of iterator
.
Sort signature (simplified):
Iterator<T>.prototype.flatMap<U>(
mapFn: (worth: T, counter: quantity) => Iterable<U> | Iterator<U>
): Iterator<U>
Instance:
assert.deepEqual(
createSyncIterator()
.flatMap((worth, counter) => new Array(counter).fill(worth))
.toArray(),
['b', 'c', 'c', 'd', 'd', 'd']
);
For extra data on .flatMap()
, see the part on the associated Array technique in “JavaScript for impatient programmers”.
Iterator strategies that return non-iterators
iterator.some(fn)
This technique returns true
if fn
returns true
for at the very least one worth of iterator
. In any other case, it returns false
.
Sort signature:
Iterator<T>.prototype.some(
fn: (worth: T, counter: quantity) => boolean
): boolean
Instance:
assert.equal(
createSyncIterator().some(x => x === 'c'),
true
);
iterator.each(fn)
This technique returns true
if fn
returns true
for each worth of iterator
. In any other case, it returns false
.
Sort signature:
Iterator<T>.prototype.each(
fn: (worth: T, counter: quantity) => boolean
): boolean
Instance:
assert.equal(
createSyncIterator().each(x => x === 'c'),
false
);
iterator.scale back(reducer, initialValue?)
This technique makes use of the operate reducer
to mix the values of iterator
right into a single worth.
Sort signature:
Iterator<T>.prototype.scale back<U>(
reducer: (accumulator: U, worth: T, counter: quantity) => U,
initialValue?: U
): U
Instance – concatenating the strings of an iterator:
assert.deepEqual(
createSyncIterator().scale back((acc, v) => acc + v),
'abcd'
);
Instance – computing the minimal of a Set of numbers:
const set = new Set([3, -2, -5, 4]);
assert.equal(
set.values().scale back((min, cur) => cur < min ? cur : min, Infinity),
-5
);
For extra data on .scale back()
, see the part on the associated Array technique in “JavaScript for impatient programmers”.
iterator.discover(fn)
This technique returns the primary worth of iterator
for which fn
returns true
. If there isn’t any such worth, it returns undefined
.
Sort signature:
Iterator<T>.prototype.discover(
fn: (worth: T, counter: quantity) => boolean
): T
Instance:
assert.equal(
createSyncIterator().discover((_, counter) => counter === 1),
'b'
);
Looping and conversion
iterator.forEach(fn)
This technique applies fn
to every worth in iterator
.
Sort signature:
Iterator<T>.prototype.forEach(
fn: (worth: T, counter: quantity) => void
): void
Instance:
const consequence = [];
createSyncIterator().forEach(x => consequence.unshift(x))
assert.deepEqual(
consequence,
['d', 'c', 'b', 'a']
);
iterator.toArray()
This technique returns the values of iterator
in an Array.
Sort signature:
Iterator<T>.prototype.toArray(): Array<T>
Instance:
assert.deepEqual(
createSyncIterator().toArray(),
['a', 'b', 'c', 'd']
);
iterator.toAsync()
This technique returns an asynchronous iterator for the values of the synchronous iterator
.
Sort signature:
Iterator<T>.prototype.toAsync(): AsyncIterator<T>
Instance:
assert.equal(
createSyncIterator() instanceof AsyncIterator,
false
);
assert.equal(
createSyncIterator().toAsync() instanceof AsyncIterator,
true
);
Utilizing the brand new API with legacy iterables
All built-in iterables routinely assist the brand new API as a result of their iterators have already got Iterator.prototype
as a prototype (and are subsequently situations of Iterator
).
Nevertheless, that’s not the case for a lot of iterables in libraries and person code.
Instance: a manually applied iterable
That is an instance of a manually applied iterable:
class MyIterable {
#values;
#index = 0;
constructor(...values) {
this.#values = values;
}
[Symbol.iterator]() {
return {
subsequent: () => {
if (this.#index >= this.#values.size) {
return {performed: true};
}
const worth = this.#values[this.#index];
this.#index++;
return {performed: false, worth};
},
};
}
}
assert.deepEqual(
Array.from(new MyIterable('a', 'b', 'c')),
['a', 'b', 'c']
);
This iterable doesn’t assist the brand new API. We will use Iterator.from()
to transform an occasion of MyIterable
to an API iterator:
const legacyIterable = new MyIterable('a', 'b', 'c');
assert.deepEqual(
Iterator.from(legacyIterable).take(2).toArray(),
['a', 'b']
);
If we wish MyIterable
to assist the brand new API, we have now to make its iterators situations of Iterator
:
class MyIterable {
[Symbol.iterator]() {
return {
__proto__: Iterator.prototype,
subsequent: () => {
},
};
}
}
That is an alternative choice:
class MyIterable {
[Symbol.iterator]() {
return Iterator.from({
subsequent: () => {
},
});
}
}
Instance: Immutable.js
The iterables supplied by the library Immutable.js don’t assist the brand new API, both. Their iterators are presently applied like this (supply):
class Iterator {
constructor(subsequent) {
this.subsequent = subsequent;
}
toString() {
return '[Iterator]';
}
[Symbol.iterator]() {
return this;
}
}
To assist the brand new API, class Iterator
must be renamed and prolong the API’s class Iterator
:
class CustomIterator extends Iterator {
}
We will additionally use Iterator.from()
to transform Immutable.js iterables to API iterators.
The best way to unify the 2 clashing iteration types
Earlier than the brand new API, JavaScript’s iteration had an iterable fashion:
- The iterable is the dominant iteration function.
- All built-in language constructs function on iterables. Programmers utilizing these constructs by no means see iterators.
- This fashion can also be utilized by Java and Python.
The brand new API has an iterator fashion:
- The iterator is the dominant iteration function. The API by no means makes use of iterables (besides to assist legacy code).
- This fashion can also be utilized by Rust.
I see two methods by which we will repair this conflict of types.
Repair 1: at all times use iterable fashion
This repair works as follows:
- We faux that the brand new API wraps iterables – loosely just like how Lodash and jQuery work.
Iterator.from()
wraps iterables and begins API technique chains.
Iteration worth | Accessing API strategies |
---|---|
Legacy iterable | Iterator.from(x).take(2) |
Legacy iterator | By no means encountered |
Non-iterator iterable (new API) | Iterator.from(str).take(2) |
Iterable iterator (new API) | Iterator.from(map.keys()).take(2) |
Professionals and cons:
-
Professional: suitable with the established order
-
Con: comparatively verbose
-
Con: The phantasm of solely working with iterables is damaged at any time when an iterator technique has parameters which are iterators. For instance, we could get technique
.zip()
sooner or later:Iterator.from(arr1).zip( Iterator.from(arr2), Iterator.from(arr3), )
-
Con: The identify
Iterator
doesn’t assist, both.
Repair 2: at all times use iterator fashion
- We faux there are solely iterators and that iterables don’t exist.
- API iterators being iterable implies that built-in language constructs can deal with them. That’s, we will faux that the constructs settle for iterators.
Iterator.from()
means getting a “correct” iterator from a knowledge construction that:- both doesn’t assist the brand new API (corresponding to library information buildings)
- or has no handy technique for creating iterators (corresponding to strings).
- Lengthy-term, this static helper technique received’t be used anymore.
Iteration worth | Accessing API strategies |
---|---|
Iterable (legacy) | Iterator.from(iterable).take(2) |
Iterator (legacy) | By no means encountered |
Non-iterator iterable (new API) | Invoke technique to create API iterator: |
arr.values().take(2) |
|
map.entries().take(2) |
|
Uncommon exception (*): | |
Iterator.from(str).take(2) |
|
Iterable iterator (new API) | arr.keys().take(2) |
(*) Strings want a technique for creating iterators that’s extra handy than [Symbol.iterator]()
.
What does that imply for brand spanking new JavaScript code?
-
Capabilities and strategies ought to settle for iterators, not iterables – particularly in TypeScript.
-
If we return a price that helps the iteration protocol, it needs to be an iterator, not an iterable. This iterator have to be an occasion of
Iterator
. -
We don’t make information buildings iterable anymore, we implement strategies that return situations of
Iterator
.- The commonest present names for such strategies are:
.keys()
,.values()
,.entries()
.iterator()
may additionally work (on account ofImage.iterator
).
- The commonest present names for such strategies are:
The next code illustrates iterator-only fashion:
for (const factor of myArray) {}
for (const factor of myArray.values()) {}
for (const [index, value] of myArray.entries()) {}
for (const factor of myDataStructure) {}
for (const factor of myDataStructure.values()) {}
operate logData1(iterable) { }
operate logData2(iterator) { }
Professionals and cons:
- Con: This fashion is a break with present practices.
- Professional: This fashion feels easier than present practices (there are solely iterators, no iterables).
- Professional: The bizarre twin nature of generator objects will not be a difficulty anymore:
- They’re allowed to be principally iterators.
- It doesn’t matter that there are two sorts of iterables (as a result of we don’t use iterables anymore).
How will this proposal have an effect on future JavaScript APIs?
Assuming all of us agree on iterator fashion:
-
It seems like upcoming ECMAScript APIs will change to iterators – for instance: The proposed new Set strategies have a parameter
different
and require techniquedifferent.keys()
to return an iterator, not an iterable. -
Strings want a technique for creating iterators that’s extra handy than
[Symbol.iterator]()
. Possibly:.toCodePoints()
. -
APIs must determine what they imply in the event that they require a parameter
iter
to be “an iterator”:- Do they solely require that
iter.subsequent()
exists (“core iterator”) or - do they require that
iter
is an occasion ofIterator
?
For technique
.keys()
talked about within the earlier merchandise, the previous method was chosen.Penalties:
- APIs that settle for core iterators could revenue from
for-of
andfor-await-of
accepting core iterators. However possibly solely ECMAScript APIs will settle for core iterators and non-built-in APIs will solely settle for situations ofIterator
. - To precise this distinction, TypeScript could should introduce a brand new interface known as (e.g.)
CoreIterator
that solely has technique.subsequent()
.
- Do they solely require that
-
In TypeScript, interface
IterableIterator
will not be wanted anymore. It’s presently the return sort of strategies corresponding to.keys()
(of Arrays, Maps, and so forth.), in order that their outcomes are accepted by language constructs that require their operands to be iterable. Nevertheless, with the ECMAScript proposal, eachIterator
is iterable.
The brand new API: asynchronous iteration
The asynchronous model of the iterator technique API is just like the synchronous model however makes use of asynchronous iteration as a substitute of synchronous iteration.
AsyncIterator.from()
The static technique AsyncIterator.from(x)
returns an occasion of AsyncIterator
:
- If
x
is an asynchronous API iterable, it returnsx[Symbol.asyncIterator]()
. - If
x
is an asynchronous API iterator, it returnsx
unchanged. - If
x
is an asynchronous legacy iterator (that doesn’t assist the brand new API), it wraps it in order that it helps the brand new API and returns the consequence. - If
x
is an asynchronous legacy iterable, it wraps the results ofx[Symbol.iterator]()
and returns it. - If
x
is a synchronous iterable (API or legacy), it returns an asynchronous iterator for its values.
Prototype strategies that return asynchronous iterators
The API for asynchronous iterators gives asynchronous analogs of the synchronous iterator strategies that return iterators – for instance:
assert.deepEqual(
await arrayFromAsync(
createAsyncIterator().filter(x => x <= 'b')
),
['a', 'b']
);
assert.deepEqual(
await arrayFromAsync(
createAsyncIterator().map(x => x + x)
),
['aa', 'bb', 'cc', 'dd']
);
We used these helper capabilities:
async operate* createAsyncIterator() {
yield 'a'; yield 'b'; yield 'c'; yield 'd';
}
async operate arrayFromAsync(asyncIterator) {
const consequence = [];
for await (const worth of asyncIterator) {
consequence.push(worth);
}
return consequence;
}
As an apart: Array.fromAsync()
is an ECMAScript proposal.
AsyncIterator.prototype.flatMap()
The asynchronous iterator technique .flatMap()
is the one case the place not solely the return sort modifications, but additionally the kind of the parameter. Its sort signature is:
AsyncIterator<T>.prototype.flatMap<U>(
mapFn: (
worth: T, counter: quantity
) => Iterable<U> | Iterator<U> | AsyncIterable<U> | AsyncIterator<U>
): AsyncIterator<U>
In different phrases: The callback mapFn
can return iterables or iterators which are both synchronous or asynchronous.
Prototype strategies that return Guarantees for values
If a synchronous iterator technique returns non-iterator values, then its asynchronous model returns Guarantees for these values. That’s why we use await
in line A, B, C and D:
async operate* createAsyncIterator() {
yield 'a'; yield 'b'; yield 'c'; yield 'd';
}
assert.deepEqual(
await createAsyncIterator().toArray(),
['a', 'b', 'c', 'd']
);
assert.deepEqual(
await createAsyncIterator().scale back((acc, v) => acc + v),
'abcd'
);
assert.equal(
await createAsyncIterator().some(x => x === 'c'),
true
);
assert.equal(
await createAsyncIterator().discover(x => x === 'c'),
'c'
);
For looping over asynchronous iterators we will use .forEach()
and can usually await the empty Promise it returns:
await createAsyncIterator().forEach(
x => console.log(x)
);
console.log('DONE');
We will additionally use for-await-of
:
for await (const x of createAsyncIterator()) {
console.log(x);
}
console.log('DONE');
Implementations of the iterator helpers
The advantages of the brand new iterator strategies
Profit: extra operations for information buildings that assist iteration
With the brand new iterator strategies, any information construction that helps iteration positive factors extra operations. For instance, Units don’t assist the operations filter
and map
. Because of the brand new iterator strategies, they now do:
assert.deepEqual(
new Set(
new Set([-5, 2, 6, -3]).values().filter(x => x >= 0)
),
new Set([2, 6])
);
assert.deepEqual(
new Set(
new Set([-5, 2, 6, -3]).values().map(x => x / 2)
),
new Set([-2.5, 1, 3, -1.5])
);
Observe that new Set()
accepts iterables and subsequently iterable iterators (line A and line B).
Profit: incremental processing
One essential good thing about iteration is that information customers that additionally produce iterated information, course of information incrementally. For example, think about code that reads a textual content file, places the string '> '
earlier than every line and logs the consequence.
If we use an Array, we have now to learn the entire file earlier than we will log the primary line.
readFileSync('information.txt')
.break up(/r?n/)
.map(line => '> ' + line)
.forEach(line => console.log(line))
;
If we use iteration, we will log the primary line shortly after studying it. With the proposed new API, this might appear like this:
createReadableStream('information.txt')
.pipeThrough(new ChunksToLinesStream())
[Symbol.asyncIterator]()
.map(line => '> ' + line)
.forEach(line => console.log(line))
;
We now have had mills for this sort of incremental processing for some time. Now we even have the iterator strategies.