I'm trying to get the following async generator to work:
class MyIterator {
constructor(m) {
this.collection = m;
}
async *[Symbol.iterator]() {
for (let item of this.collection) {
const resultItem = await Promise.resolve(item)
console.log("item: ", resultItem)
yield resultItem
}
}
}
(async () => {
const iterator = new MyIterator([1,2,3])
let times = 0
for await (let thing of iterator) {
console.log("thing: ", thing)
// this is here to avoid an infinite loop
times++
if (times > 1000) break
}
})()
But it ends up in an infinite loop, and thing
is always undefined.
item: 1
thing: undefined
item: 2
thing: undefined
item: 3
thing: undefined (x999)
I've tried a similar code, but this time without the Promise/async
behaviour, and it seems to work just fine.
class MyIterator {
constructor(m) {
this.collection = m;
}
*[Symbol.iterator]() {
for (let item of this.collection) {
console.log("item: ", item)
yield item
}
}
}
const iterator = new MyIterator([1,2,3])
for (let thing of iterator) {
console.log("thing: ", thing)
}
item: 1
thing: 1
item: 2
thing: 2
item: 3
thing: 3
The for await..of
construct will attempt to iterate over an async iterator.
An async iterator is defined using the @@asyncIterator
well-known symbol:
class MyIterator {
constructor(m) {
this.collection = m;
}
async *[Symbol.asyncIterator]() { //<-- this is async
for (let item of this.collection) {
const resultItem = await Promise.resolve(item)
//console.log("item: ", resultItem)
yield resultItem
}
}
}
(async () => {
const iterator = new MyIterator([1,2,3])
let times = 0
for await (let thing of iterator) {
//no infinite loop
console.log("thing: ", thing)
}
})()
for await..of
can also consume plain iterables that produce promises:
const promiseArray = [Promise.resolve("a"), Promise.resolve("b"), Promise.resolve("c")];
(async function() {
for await(const item of promiseArray) {
console.log(item);
}
})()
Attempting to make a regular iterator that is an async method/function does not work.
If you want to keep your @@iterator
defined method your the best choice is to make it produce promises instead:
class MyIterator {
constructor(m) {
this.collection = m;
}
*[Symbol.iterator]() { // not async
for (let item of this.collection) {
yield Promise.resolve(item); //produce a promise
}
}
}
(async () => {
const iterator = new MyIterator([1,2,3])
let times = 0
for await (let thing of iterator) {
console.log("thing: ", thing)
}
})()
Although, that's might be a bad practice if any of the promises rejects:
const wait = (ms, val) =>
new Promise(res => setTimeout(res, ms, val));
const fail = (ms, val) =>
new Promise((_, rej) => setTimeout(rej, ms, val));
const arr = [
wait(100, 1),
wait(150, 2),
fail(0, "boom"),
wait(200, 3)
];
(async function(){
try {
for await (const item of arr) {
console.log(item);
}
} catch (e) {
console.error(e);
}
})()
/* result in the browser console:
Uncaught (in promise) boom
1
2
boom
*/
However, be aware that there is a difference in semantics between these:
value
and done
properties.const syncIterable = {
[Symbol.iterator]() {
return {
next() {
return {value: 1, done: true}
}
}
}
}
const syncIterator = syncIterable[Symbol.iterator]();
console.log("sync IteratorResult", syncIterator.next());
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
next() {
return Promise.resolve({value: 2, done: true});
}
}
}
}
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
asyncIterator.next().then(result => console.log("async IteratorResult", result));
value
is a promise:const promiseSyncIterable = {
[Symbol.iterator]() {
return {
next() {
return {value: Promise.resolve(3), done: true}
}
}
}
}
const promiseSyncIterator = promiseSyncIterable[Symbol.iterator]();
const syncPromiseIteratorResult = promiseSyncIterator.next();
console.log("sync IteratorResult with promise", syncPromiseIteratorResult);
syncPromiseIteratorResult.value
.then(value => console.log("value of sync IteratorResult with promise", value));
Side-note on nomenclature: MyIterator
is not an iterator. An iterator is an object with a next()
method which produces an IteratorResult. An object that you can iterate over has an @@iterator
(or @@asyncIterable
) method and it is called iterable (or async iterable respectively).