C12Z
C12Z

Reputation: 148

How to return from yield *

Is there any way to return from a yield * asyncIterator?

Background:

Given an async iterator like this:

const asyncIterator = {
  next () {
    return new Promise(resolve => setTimeout(() => resolve('next'), 500))
  },
  return () {
    return Promise.resolve('return')
  },
  [Symbol.asyncIterator] () {
    return this
  }
}

the following will never invoke its return method:

async * yieldFromIterator () {
  yield 'start'
  yield * asyncIterator
}

const yieldingIterator = yieldFromIterator()
console.log(await yieldingIterator.next()) // 'start'
console.log(await yieldingIterator.next()) // 'next'
console.log(await yieldingIterator.return()) // never resolved

neither does this:

async * yieldFromIterator () {
  yield 'start'
  for await (const result of asyncIterator) {
    yield result
  }
}

const yieldingIterator = yieldFromIterator()
console.log(await yieldingIterator.next()) // 'start'
console.log(await yieldingIterator.next()) // 'next'
console.log(await yieldingIterator.return()) // never resolved

while the following of course does:

async * returnIterator () {
  yield 'start'
  return asyncIterator
}

const returningIterator = returnIterator()
console.log(await returningIterator.next()) // 'start'
console.log(await returningIterator.next()) // 'next'
console.log(await returningIterator.return()) // 'return'

Thank you very much :)

Upvotes: 2

Views: 580

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1075895

Your asyncIterator has an error: The fulfillment value of the promise must be a result object (an object implementing the IteratorResult interface, which has the shape {done, value}). But you're fulfilling the promise with a string instead.

If you update it to return a result object instead, you'll see the return:

const asyncIterator = {
  next () {
    return new Promise(resolve => setTimeout(() => resolve({value: 'next', done: false}), 500))
  },
  return () {
    return Promise.resolve({value: 'return', done: true})
  },
  [Symbol.asyncIterator] () {
    return this
  }
}

async function * yieldFromIterator () {
  yield 'start'
  yield * asyncIterator
}

(async function() {
  const yieldingIterator = yieldFromIterator()
  console.log(await yieldingIterator.next())   // {value: 'start', done: false}
  console.log(await yieldingIterator.next())   // {value: 'next', done: false}
  console.log(await yieldingIterator.return()) // {value: 'return', done: true}
})().catch(error => {
  console.error(error)
})
.as-console-wrapper {
    max-height: 100% !important;
}


Separately, asyncIterator has another issue (not necessarily a bug): It doesn't inherit from %AsyncIteratorPrototype% and the chain below it. In theory, code could add methods to %AsyncIteratorPrototype%, %IteratorPrototype%, etc., and those would be missing from asyncIterator.

In general, async generator functions are the best way to create async iterator objects (because async generators are async iterators), just like non-async generator functions are the best way to create iterators. You get the benefit of yield syntax and inheriting from the standard prototypes.

That said, if you implemented asyncIterator with an async generator function, you wouldn't see your 'return' string in response to that return call. You'd need to call next, and use return in yieldFromIterator:

const delay = (ms, ...args) => new Promise(resolve => setTimeout(resolve, ms, ...args));

const asyncIterator = (async function*() {
  await delay(500);
  yield 'next';
  return 'return';
})();

async function * yieldFromIterator () {
  yield 'start'
  return yield * asyncIterator // <=== Added `return`
}

(async function() {
  const yieldingIterator = yieldFromIterator()
  console.log(await yieldingIterator.next()) // {value: 'start', done: false}
  console.log(await yieldingIterator.next()) // {value: 'next', done: false}
  console.log(await yieldingIterator.next()) // {value: 'return', done: true}
  // Changed to `next` −−−−−−−−−−−−−−^
})().catch(error => {
  console.error(error)
})
.as-console-wrapper {
    max-height: 100% !important;
}

Upvotes: 3

Related Questions