Reputation: 148
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
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