4

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.

pwilcox
  • 5,542
  • 1
  • 19
  • 31
  • Do you consider an option of implementing a custom extended iterator or you need a solution that works with native iterators? – Shlang Apr 11 '20 at 23:10
  • If a native iterator can be wrapped or fed into the extended iterator, and if you don't have to iterate it to make that happen, then yes, I would consider it. – pwilcox Apr 12 '20 at 00:32

2 Answers2

5

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);
pwilcox
  • 5,542
  • 1
  • 19
  • 31
  • I cannot imagine any better approach. The only alternative is to implement custom iterators (e.g. [like in this article](https://blog.codewithdan.com/using-the-iterator-pattern-in-javascript/)) but this way does not allow using native syntax. Basically, native iterators can return mutable objects and run side effects, thus you cannot get its `next` value without changing the inner state. – Shlang Apr 12 '20 at 12:55
  • Hey @Shlang, thanks for looking into it and for the article reference. If I ever successfully use any of the article's concepts i may update my question or answer. – pwilcox Apr 12 '20 at 16:23
4

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);
}
Shlang
  • 2,495
  • 16
  • 24
  • Thanks for your answer. Haven't yet had time to review it deeply, but it's on my radar. – pwilcox Apr 18 '20 at 15:49
  • Okay, definitely voting this up. I'll leave unaccepted to give a little more time for other ideas. But thanks! – pwilcox Apr 18 '20 at 16:08