cyberixae
cyberixae

Reputation: 973

Capturing the Return Value of a JavaScript Iterator

Consider the following iterator. It yields strings 'foo' and 'bar' and then returns 'quux' as a return value. I can use Array.from to extract the yields from the iterator but if I do this, I have no way of reading out the return value. The return value is no longer returned by the iterator since Array.from already received (and discarded?) it as part of the protocol.

const iterator = (function* () {
  yield 'foo'
  yield 'bar'
  return 'quux'
})()
const [foo, bar] = Array.from(iterator)
const quux = // ???

The only solution I can think of is writing a spy procedure that monitors the iteration and stores the return value into a predefined variable as a side effect before Array.from discards the return value. Are there better alternative ways of accessing the return value?

let quux
const spy = function* (i) { /* extract return value from i and store it to quux */ }
const [foo, bar] = Array.from(spy(iterator))

Upvotes: 5

Views: 1177

Answers (2)

cyrilluce
cyrilluce

Reputation: 985

Another solution:


function extractIterator<TItem, TResult>(iter: Generator<TItem, TResult>) {
    const items: TItem[] = [];
    let result: IteratorResult<TItem, TResult>;
    while ((result = iter.next())) {
        if (result.done) {
            return {
                items,
                result: result.value,
            };
        }
        items.push(result.value);
    }
    throw 'unreachable';
}

Upvotes: -1

CherryDT
CherryDT

Reputation: 29012

You are right - when the iterator next function returns { done: true, value: 'whatever' } the value is silently discarded.

As you already said, you could write a wrapper function (or a class, if you wanted to). But instead of saving it to an external variable, you could make it convert this last value to a regular iterated value.

For example:

function* yieldFinal (iterator) {
  let done, value
  do {
    ({ done, value } = iterator.next())
    yield value
  } while (!done)
}

const iterator = (function* () {
  yield 'foo'
  yield 'bar'
  return 'quux'
})()

const [foo, bar, quux] = Array.from(yieldFinal(iterator))

console.log(foo, bar, quux)

(Note: If there was no explicit return value, then you'd get undefined for the last value.)

But, then the question is, why do you even need to go to these lengths? Instead of writing return 'quux' you could write yield 'quux'; return and achieve the same thing without going through all the hoops...

Upvotes: 3

Related Questions