See the two code blocks below:
for await (const {key, value} of ctx.stub.getStateByRange(startKey, endKey)) {}
and
let allCarsRangeIterator = await ctx.stub.getStateByRange(startKey, endKey);
for (const {key, value} of allCarsRangeIterator ) {}
When I use the first one, everything works, but when I use the second one, I get an error saying allCarsRangeIterator is not iterable.
Why are these two not equivalent ?
I found this to be a very interesting question and dug around for a bit. Let's see if I can answer this properly:
First, we need to be clear about iterators and generators, so I encourage everyone to go through the linked documentation.
The following seems to be a generator:
ctx.stub.getStateByRange(startKey, endKey)
As per the docs, a generator is a special type of iterator that can be iterated over only once. On top of that, it seems to be a generator that does some async computation. Let's mock this with a simpler function:
async function* foo() {
yield 1;
yield new Promise((resolve) => setTimeout(resolve, 2000, 2));
yield 3;
}
As per the docs, in order for an object to be iterable, it needs to implement the Symbol.asyncIterator
(async) or Symbol.iterator
(sync) protocol. Let's check this:
async function* foo() {
yield 1;
yield new Promise((resolve) => setTimeout(resolve, 2000, 2));
yield 3;
}
(async function () {
const obj1 = foo();
console.log(typeof obj1[Symbol.iterator] === "function"); // false
console.log(typeof obj1[Symbol.asyncIterator] === "function"); // true
})();
Now let's see the output if we use await
.
async function* foo() {
yield 1;
yield new Promise((resolve) => setTimeout(resolve, 2000, 2));
yield 3;
}
(async function () {
const obj1 = await foo();
console.log(typeof obj1[Symbol.iterator] === "function"); // false
console.log(typeof obj1[Symbol.asyncIterator] === "function"); // true
})();
It's still the same. This conforms to the error you got: allCarsRangeIterator is not iterable
, because obj1
does not implement the Symbol.iterator
protocol but rather the Symbol.asyncIterator
protocol.
So, when you are trying to use for (const num of await foo())
, it is a synchronous code, so it is going to look for Symbol.iterator
protocol implementation. But since your function is an async generator, it only has Symbol.asyncIterator
protocol implemented.
The only way to iterate over an async generator is therefore:
for await (const num of foo()) {
console.log(num);
}
You will also see that this is valid (although it looks weird)
for await (const num of await foo()) {
console.log(num);
}
Since (const num of await foo())
still has the Symbol.asyncIterator
protocol implemented.
In fact, in VSCode, you will see a warning whenever you type for (const num of await foo())
:
Type 'AsyncGenerator<any, void, unknown>' must have a '[Symbol.iterator]()' method that returns an iterator. ts(2488)
and const obj1 = await foo()
shows the warning:
'await' has no effect on the type of this expression. ts(80007)
Additional resources: