4

Is it possible to access the previous value of an iterator, for example:

function* gen () {
   yield* [0,1,2,3]
}

var it = gen();

it.next().value // 0
it.next().value // 1
it.last().value // would be awesome if this was 0

This is strictly a curiosity question. I've been playing with generators lately and was wondering if anyone uses generators/iterators in this manner?

If so, I would love to see your solution. (Doesn't need to follow the above syntax) -Thanks

Andrew Li
  • 55,805
  • 14
  • 125
  • 143
Joseph Barnes
  • 620
  • 5
  • 10

3 Answers3

5

JS iterators only know one direction: forwards. Meaning, if you would want to use your custom iterator with a for-loop or the like, it would ignore your custom properties. You could however create your own custom iterator for your own use only:

const obj = {
   values: [0,1,2,3],
   [Symbol.iterator]() {
     const values = this.values;
     const min = 0;
     const max = values.length - 1;

     let index = -1;

     return {
       next() {
        index = Math.min(index + 1, max);
        return { value: values[index], done: index >= max };
       },
       prev() {
        index = Math.max(index - 1, min);
        return { value: values[index], done: index <= min };
       }
     }
   }
}

var it = obj[Symbol.iterator]();

console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.prev().value); // would be awesome if this was 0
console.log(it.prev().value); // would be awesome if this was 0
console.log(it.prev().value); // would be awesome if this was 0
console.log(it.next().value); // would be awesome if this was 1
nils
  • 25,734
  • 5
  • 70
  • 79
4

You can wrap the generator in a function that saves previous yields:

const wrapGenerator = gen => (...args) => {
  const it = gen(...args);
  let previous, current;
  return {
    next: () => {
      const r = it.next();
      previous = current;
      current = r.value;
      return r;
    },
    previous: () => ({ value: previous })
  };
};

const gen = wrapGenerator(function * () {
  yield * [0, 1, 2, 3];
});

const it = gen();

console.log(it.next().value);
console.log(it.next().value);
console.log(it.previous().value);

I changed the function name to previous to avoid confusion (last could mean "the last item").

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • 1
    The OP used `last` as a reference to time, but it would be confused with *last item*. Why not call the method `prev` or `previous`? – Majid Fouladpour Jun 08 '17 at 16:13
  • @MajidFouladpour `prev` might lead to the expectation that it advances the generator in the opposite direction, which it doesn't – Bergi Jun 08 '17 at 16:16
1

No, this is not generally possible. Iterators can only be advanced, not reversed or anything else.

Of course you can write your own iterator wrapper that caches the last value and makes it accessible in whatever way you like.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375