How to sequentially combine two sync iterators in JavaScript? - javascript

Let's say we have two async iterators,
const asyncIterable1 = {
[Symbol.asyncIterator]() {
return {
i: 0,
next() {
if (this.i < 3) {
return Promise.resolve({ value: this.i++, done: false });
}
return Promise.resolve({ done: true });
}
};
}
};
const asyncIterable2 = {
[Symbol.asyncIterator]() {
return {
i: 3,
next() {
if (this.i < 5) {
return Promise.resolve({ value: this.i++, done: false });
}
return Promise.resolve({ done: true });
}
};
}
};
Now, is there a way to combine these two iterators into one iterator that would return a sequence of 0,1,2 and then 3,4?

Yeah, I'd use yield* for that:
const combine = (a, b) => (function* () { yield* a; yield* b; })();
const iterator = combine(
(function* () { yield 1; yield 2; })(),
(function* () { yield 3; yield 4; })()
);
console.log(iterator.next(), iterator.next(), iterator.next(), iterator.next(), iterator.next());
This works analogously for async iterators. You'll loose the return value ("the done yield") of the first iterator though. You could capture it however (the value yield* evaluates to).
For sure if you're among the people that like to reinvent wheels, you can also implement such functionality "by hand" without generator functions:
function combine(...iterators) {
let pos = 0, iterator = iterators[pos];
return {
next() {
let result = { done: true };
do {
result = iterator.next();
if(!result.done) break;
iterator = iterators[ pos++ ];
} while(iterator)
return result;
}
};
}
const iterator = combine(
(function* () { yield 1; yield 2; })(),
(function* () { yield 3; yield 4; })()
);
console.log(iterator.next(), iterator.next(), iterator.next(), iterator.next(), iterator.next());

In addition to Jonas' great answer, we could generalize a bit further and combine an arbitrary number of iterators:
let combine = function*(...iterators) {
for (let it of iterators) yield* it;
};

Related

JavaScript adding "return" method to an iterator doesn't properly close the iterator

I am learning the JavaScript ES6 iterator pattern and came across this problem:
const counter = [1, 2, 3, 4, 5];
const iter = counter[Symbol.iterator]();
iter.return = function() {
console.log("exiting early");
return { done: true };
};
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
// 1
// 2
// 3
// exiting early
// ---
// 4
// 5
So I added a return method definition to the iterator that I extracted from an array. Although the return method got called, it didn't actually close the iterator. By contrast, if I define the iterator return method in definition, it will work as expected:
class Counter {
[Symbol.iterator]() {
let count = 1;
return {
next() {
if (count <= 5) {
return {
done: false,
value: count++
}
} else {
return {
done: true,
value: undefined
}
}
},
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
}
}
}
const myCounter = new Counter();
iter = myCounter[Symbol.iterator]();
for (let i of myCounter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of myCounter) {
console.log(i);
}
// 1
// 2
// 3
// exiting early
// ---
// 1
// 2
// 3
// 4
// 5
My question is, why did I get this unexpected behavior? I assume that if the return method didn't get called, then the iterator will not close until it reaches the very end by calling next. But adding return property will properly "call" the return method since I got the console log, but doesn't actually terminate the iterator even if I returned { done: true } in the return method.
Neither of your two return methods actually close the iterator. To achieve that, they need to record the new state of the iterator, and by that cause the next method to also return {done: true} in all subsequent calls - that's what "closed" actually means.
We can see this behaviour in action with a generator:
const iter = function*(){ yield 1; yield 2; yield 3; }();
console.log(iter.next());
console.log(iter.return());
console.log(iter.next());
Your first snippet has the problem that you've overwritten iter.return, and your method gets called (as seen from the log) but it never actually closes iter. The underlying problem is that array iterators cannot be closed, they don't normally have a return method at all. You'd have to overwrite the iter.next method as well to simulate this.
Your second snippet has the problem that it's not actually trying to iterate the iter, but it's iterating the myCounter twice which creates a new iterator object for each loop. Instead we need to use a [Symbol.iterator] method that returns the same object multiple times, easiest done by having Counter implement the iterator interface itself. We can now reproduce the unexpected behaviour:
class Counter {
count = 1;
[Symbol.iterator]() {
return this;
}
next() {
if (this.count <= 5) {
return {done: false, value: this.count++ };
} else {
return {done: true, value: undefined};
}
}
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
}
const iter = new Counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
To fix the behaviour, we would close the iterator by having the return method set the count beyond 5:
class Counter {
count = 1;
[Symbol.iterator]() {
return this;
}
next() {
if (this.count <= 5) {
return {done: false, value: this.count++ };
} else {
return {done: true, value: undefined};
}
}
return() {
this.count = 6;
// ^^^^^^^^^^^^^^^
console.log('exiting early');
return { done: true, value: undefined };
}
}
const iter = new Counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i); // not executed!
}
Your example can be simplified as
let count = 1;
const iter = {
[Symbol.iterator]() { return this; },
next() {
if (count <= 5) {
return {
done: false,
value: count++
}
} else {
return {
done: true,
value: undefined
}
}
},
return() {
console.log('exiting early');
return { done: true, value: undefined };
}
};
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}
so iter is just a normal object. You are passing it to a for..of loop twice.
You are making incorrect assumptions about how the interface for iterators works. The core issue is that there is nothing in this code that stores and tracks the fact that iter has returned done: true once, and thus should continue to do so. If that is the behavior you want, you need to do that yourself, e.g.
let count = 1;
let done = false;
const iter = {
[Symbol.iterator]() { return this; },
next() {
if (!done && count <= 5) {
return {
value: count++
}
} else {
done = true;
return { done };
}
},
return() {
done = true;
console.log('exiting early');
return { done };
}
};
A for..of loop essentially calls .next() until the return result is done: true, and calls .return in some cases. It is up to the implementation of the iterator itself to ensure that it properly enters a "closed" state.
All of this can also be simplified by using a generator function, since generator objects have that internal "closed" state included automatically as a side-effect of the function having returned, e.g.
function* counter() {
let counter = 1;
while (counter <= 5) yield counter++;
}
const iter = counter();
for (let i of iter) {
console.log(i);
if (i >= 3) {
break;
}
}
console.log('---');
for (let i of iter) {
console.log(i);
}

How to peek at the next value in a Javascript Iterator

Let's say I have an iterator:
function* someIterator () {
yield 1;
yield 2;
yield 3;
}
let iter = someIterator();
... that I look at the next element to be iterated:
let next = iter.next(); // {value: 1, done: false}
... and I then use the iterator in a loop:
for(let i of iterator)
console.log(i);
// 2
// 3
The loop will not include the element looked at. I wish to see the next element while not taking it out of the iteration series.
In other words, I wish to implement:
let next = peek(iter); // {value: 1, done: false}, or alternatively just 1
for(let i of iterator)
console.log(i);
// 1
// 2
// 3
... and I wan't to do it without modifying the code for the iterable function.
What I've tried is in my answer. It works (which is why I made it an answer), but I worry that it builds an object that is more complex than it has to be. And I worry that it will not work for cases where the 'done' object is something different than { value = undefined, done = true }. So any improved answers are very much welcome.
Instead of a peek function, I built a peeker function that calls next, removing the element from the iterator, but then adds it back in by creating an iterable function that first yields the captured element, then yields the remaining items in the iterable.
function peeker(iterator) {
let peeked = iterator.next();
let rebuiltIterator = function*() {
if(peeked.done)
return;
yield peeked.value;
yield* iterator;
}
return { peeked, rebuiltIterator };
}
function* someIterator () { yield 1; yield 2; yield 3; }
let iter = someIterator();
let peeked = peeker(iter);
console.log(peeked.peeked);
for(let i of peeked.rebuiltIterator())
console.log(i);
Just a bit different idea is to use wrapper that makes an iterator kind of eagier.
function peekable(iterator) {
let state = iterator.next();
const _i = (function* (initial) {
while (!state.done) {
const current = state.value;
state = iterator.next();
const arg = yield current;
}
return state.value;
})()
_i.peek = () => state;
return _i;
}
function* someIterator () { yield 1; yield 2; yield 3; }
let iter = peekable(someIterator());
let v = iter.peek();
let peeked = iter.peek();
console.log(peeked.value);
for (let i of iter) {
console.log(i);
}

Using javascript's Symbol.asyncIterator with for await of loop

I am trying to understand javascript's Symbol.asyncIterator and for await of. I wrote some simple code and it throws an error saying:
TypeError: undefined is not a function
on the line which tries to use for await (let x of a).
I could not understand the reason for it.
let a = {}
function test() {
for(let i=0; i < 10; i++) {
if(i > 5) {
return Promise.resolve(`Greater than 5: (${i})`)
}else {
return Promise.resolve(`Less than 5: (${i})`)
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) { // LINE THAT THROWS AN ERROR
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
I create an empty object a and insert a key Symbol.asyncIterator on the same object and assign it a function named test that returns a Promise. Then I use for await of loop to iterate over all the values that the function would return.
What am I doing incorrectly?
PS: I am on the Node version 10.13.0 and on the latest version of Chrome
To be a valid asyncIterator, your test function must return an object with a next method that returns a promise of a result object with value and done properties. (Technically, value is optional if its value would be undefined and done is optional if its value would be false, but...)
You can do that in a few ways:
Completely manually (awkward, particularly if you want the right prototype)
Half-manually (slightly less awkward, but still awkward to get the right prototype)
Using an async generator function (simplest)
You can do it completely manually (this doesn't try to get the right prototype):
function test() {
let i = -1;
return {
next() {
++i;
if (i >= 10) {
return Promise.resolve({
value: undefined,
done: true
});
}
return Promise.resolve({
value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
done: false
});
}
};
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
You can do it half-manually writing a function that returns an object with an async next method (still doesn't try to get the right prototype):
function test() {
let i = -1;
return {
async next() {
++i;
if (i >= 10) {
return {
value: undefined,
done: true
};
}
return {
value: i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`,
done: false
};
}
};
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
Or you can just use an async generator function (easiest, and automatically gets the right prototype):
async function* test() {
for (let i = 0; i < 10; ++i) {
yield i > 5 ? `Greater than 5: (${i})` : `Less than 5: (${i})`;
}
}
let a = {
[Symbol.asyncIterator]: test
};
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
About prototypes: All async iterators you get from the JavaScript runtime itself inherit from a prototype that provides the very basic feature of ensuring the iterator is also iterable (by having Symbol.iterator be a function returning this). There's no publicly-available identifer or property for that prototype, you have to jump through hoops to get it:
const asyncIteratorPrototype =
Object.getPrototypeOf(
Object.getPrototypeOf(
async function*(){}.prototype
)
);
Then you'd use that as the prototype of the object with the next method that you're returning:
return Object.assign(Object.create(asyncIteratorPrototype), {
next() {
// ...
}
});
The test function must not return a promise, but an Iterator (an object with a next() ) method, that method then has to return a Promise (which makes it an async iterator) and that Promise has to resolve to an object containing a value and a done key:
function test() {
return {
next() {
return Promise.resolve({ value: "test", done: false });
}
};
}
Now while that works, it is not that useful yet. You could however create the same behaviour with an async generator function:
async function* test() {
await Promise.resolve();
yield "test";
}
Or in your case:
async function* test() {
for(let i = 0; i < 10; i++) {
if(i > 5) {
await Promise.resolve();
yield `Greater than 5: (${i})`;
}else {
await Promise.resolve();
yield `Less than 5: (${i})`;
}
}
}
You should make test an async generator function instead, and yield instead of return:
let a = {}
async function* test() {
for(let i=0; i < 10; i++) {
if(i > 5) {
yield Promise.resolve(`Greater than 5: (${i})`)
}else {
yield Promise.resolve(`Less than 5: (${i})`)
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
It looks like the test function needs to be async so that the x in the for await gets unwrapped, even though test doesn't await anywhere, otherwise the x will be a Promise that resolves to the value, not the value itself.
yielding Promise.resolve inside an async generator is odd, though - unless you want the result to be a Promise (which would require an extra await inside the for await loop), it'll make more sense to await inside the async generator, and then yield the result.
const delay = ms => new Promise(res => setTimeout(res, ms));
let a = {}
async function* test() {
for(let i=0; i < 10; i++) {
await delay(500);
if(i > 5) {
yield `Greater than 5: (${i})`;
}else {
yield `Less than 5: (${i})`;
}
}
}
a[Symbol.asyncIterator] = test;
async function main() {
for await (let x of a) {
console.log(x)
}
}
main()
.then(r => console.log(r))
.catch(err => console.log(err))
If you didn't make test a generator, test would have to return an iterator (an object with a value property and a next function).

Distribute iterator items over subiterators

I am playing with iterators in javascript. I found a way of chaining them so I don't have to nest them, just for readability.
It kind of works exactly like an array map chain.
What I like to do is distribute the items of the generator over a few sub iterators, catch the results and feed them to the next iterator. Such that the outcome will be this:
4
6
8
7
9
11
Given this piece of code:
"use strict";
const _ = require('lodash');
let things = chain([
gen,
addOne,
distribute([
addOne,
addTwo,
addThree
]),
addOne
]);
for(let thing of things) {
console.log(thing);
}
//////////////// DEFENITIONS ////////////////////
function* gen() {
yield* [1, 2, 3, 4, 5, 6];
}
function* addOne(iterator) {
for(let item of iterator) {
yield (item + 1)
}
}
function* addTwo(iterator) {
for(let item of iterator) {
yield (item + 2)
}
}
function* addThree(iterator) {
for(let item of iterator) {
yield (item + 3)
}
}
const distribute = _.curry(function* (iterators, iterator) {
// magic
});
function chain(iterators) {
return iterators.reduce((prevIterator, thisIterator, index) => {
if(index === 0) {
return thisIterator();
}
return thisIterator(prevIterator);
}, null);
}
Eventually i would like to add a distribution function to the distribution iterator, so it can determine which item to pass to which subiterator. For now it is just based on the order.
Question: How do I write a distribution iterator that takes a few subiterators as an argument and passes the results through to the next iterator.
It seems overly complex, but it works
const distribute = _.curry(function* (iterators, mainIterator) {
let iteratorIndex = 0;
let done = [];
for(let iterator of iterators) {
iterators[iteratorIndex] = iterator(mainIterator);
done.push(false);
iteratorIndex++;
}
while(true) {
let iteratorIndex = 0;
for(let iterator of iterators) {
let next = iterator.next();
done[iteratorIndex] = next.done;
if(!next.done) {
yield next.value;
}
iteratorIndex++;
}
if(done.every(done => done)) {
return;
}
}
});
And finally with a distribution function:
const distribute = _.curry(function* (genIteratorIndex, iterators, mainIterator) {
let iteratorIndex = 0;
let done = [];
// instantiate iterators
for(let iterator of iterators) {
iterators[iteratorIndex] = iterator(mainIterator);
done.push(false);
iteratorIndex++;
}
// Pass stuff through
while(true) {
let next = iterators[genIteratorIndex.next().value].next();
done[iteratorIndex] = next.done;
if(!next.done) {
yield next.value;
}
if(done.every(done => done === true)) {
return;
}
}
});
function* subSequent(len) {
let curr = 0;
while(true) {
if(curr === len) {
curr = 0;
}
yield curr;
curr++;
}
}
let things = chain([
gen,
addOne,
distribute(subSequent(3), [
addOne,
addTwo,
addThree
]),
addOne
]);

hasNext() for ES6 Generator

How would I implement hasNext() method for a generator. I have tried many options like adding the generator as a return statement and yielding from the closure. Getting the first value printing it and then using the while etc, but none of them actually worked.
I know I can use for of or while like How to loop the JavaScript iterator that comes from generator? but still wondering if I can add hasNext().
function *range(start,end){
while(start < end){
yield start;
start++
}
}
let iterator = range(1,10);
// so I can do something like this.
while(iterator.hasNext()){
console.log(iterator.next().value);
}
The simple non-for…of way to loop an iterator is
for (let iterator = range(1, 10), r; !(r = iterator.next()).done; ) {
console.log(r.value);
}
If you really want to use hasNext, you can do that as well, but it's a bit weird:
const iterator = range(1, 10);
iterator.hasNext = function hasNext() {
const r = this.next();
this.current = r.value;
return !r.done;
};
while (iterator.hasNext()) {
console.log(iterator.current);
}
You can create and return an object from range having hasNext defined as Boolean by checking if start + 1 < end; recursively call a function, hasNext if generator .next().done is false and hasNext property of object at generator.next().value is set to true, else perform other task
function* range(start, end) {
let next = () => start + 1 < end;
let g = {
hasNext: next(),
currentValue: start
};
while (start < end) {
yield g;
g.currentValue = ++start;
g.hasNext = next();
}
}
let iterator = range(1, 10);
function hasNext(gen) {
let curr = gen.next();
if (!curr.done) {
if (curr.value.hasNext) {
console.log(`hasNext:${curr.value.hasNext}`
, `currentValue:${curr.value.currentValue}`);
hasNext(gen);
} else {
console.log(JSON.stringify(curr.value, null, 2));
}
}
}
hasNext(iterator);
I created this simple module. It allows you to decorate any iterator and obtain information whether there is more elements.
https://www.npmjs.com/package/iterable-has-next
Usage is as simple as that:
//...
const { extendIterator } = require('iterable-has-next')
const extendedIterator = await extendIterator(yourIterator)
while (extendedIterator.hasNext()) {
const { value } = await extendedIterator.next()
console.log(value)
}
//...
You can try it out
https://repl.it/repls/YummyWrathfulRhombus
Here's another alternative. Instead of using a generator function, create a regular function and have it return an object with custom implementations for hasNext() and next() and add a user-defined iterable for for...of loops:
function getRange(start, end) {
return {
hasNext() {
return start <= end;
},
next() {
return start <= end ? start++ : undefined;
},
*[Symbol.iterator]() {
while(start <= end) yield start++;
}
};
}
console.log('Test iteration A');
let iteratorA = getRange(1, 10);
while(iteratorA.hasNext()) {
console.log(iteratorA.next());
}
console.log('\n\nTest iteration B');
let iteratorB = getRange(20, 30);
for(let index of iteratorB) {
console.log(index);
}

Categories

Resources