admin管理员组文章数量:1434943
I know there are plenty of resources explaining for await...of
, but I don't think I'll ever fully grasp the concept until I see an example that works the exact same way but with more basic syntax.
Thing is, if my understanding is correct, these 2 for
loops should behave the exact same way:
for(const p of iterableWithPromises) {
const q = await p;
// ...
}
for await (const q of iterableWithPromises) {
// ...
}
Is that it? Is this syntax only saving us from creating a new variable inside the loop to store the promise's result?
I know there are plenty of resources explaining for await...of
, but I don't think I'll ever fully grasp the concept until I see an example that works the exact same way but with more basic syntax.
Thing is, if my understanding is correct, these 2 for
loops should behave the exact same way:
for(const p of iterableWithPromises) {
const q = await p;
// ...
}
for await (const q of iterableWithPromises) {
// ...
}
Is that it? Is this syntax only saving us from creating a new variable inside the loop to store the promise's result?
Share Improve this question edited Nov 18, 2024 at 17:52 dumbass 27.3k4 gold badges38 silver badges74 bronze badges asked Nov 18, 2024 at 10:16 AdrianAdrian 2,0271 gold badge23 silver badges45 bronze badges 7 | Show 2 more comments2 Answers
Reset to default 4No, the 2 loops are not exactly equivalent.
First things first: This is, roughly speaking, what the equivalent of the traditional for ...of
loop looks like with normal iterables (omiting corner cases like the usage of break, continue, exceptions and returns in the loop for brevity):
// Given this iterable...
const iterable = {
[Symbol.iterator]() {
console.log('[Symbol.iterator]() called');
let i = 0;
return {
next() {
console.log('next() called');
const iteratorResult = { value: i, done: i > 3 };
i++;
return iteratorResult;
},
};
},
};
// ...this for loop...
for (const value of iterable) {
console.log(`VALUE: ${value}`);
}
// ...is equivalent to this while loop:
const iterator = iterable[Symbol.iterator]();
let iteratorResult = iterator.next();
while(!iteratorResult.done){
const value = iteratorResult.value;
console.log(`VALUE: ${value}`);
iteratorResult = iterator.next();
}
// And this would be the output:
//
// [Symbol.iterator]() called
// next() called
// VALUE: 0
// next() called
// VALUE: 1
// next() called
// VALUE: 2
// next() called
// VALUE: 3
// next() called
Now, then, with for await...of
and async iterables, the equivalent would be like this:
const makePromise = (name, seconds, value) => {
console.log(`Creating a promise named ${name} that will resolve in ${seconds} seconds with a value of ${JSON.stringify(value)}...`);
return new Promise((resolve) => {
console.log(`Promise ${name} created`);
setTimeout(() => {
console.log(`Resolving promise ${name}...`);
resolve(value);
console.log(`Promise ${name} resolved`);
}, seconds*1000)
});
}
// Given this iterable...
const iterable = {
[Symbol.asyncIterator]() {
console.log('[Symbol.asyncIterator]() called');
let i = 0;
return {
next() {
console.log('next() called');
const iteratorResult = makePromise(`promise-${i}`, i, { value: i, done: i > 3 });
i++;
return iteratorResult;
},
};
},
};
// ...this for loop...
for await (const value of iterable) {
console.log(`VALUE: ${value}`);
}
// ...is equivalent to this while loop:
const iterator = iterable[Symbol.asyncIterator]();
let iteratorResult = await iterator.next();
while(!iteratorResult.done){
const value = iteratorResult.value;
console.log(`VALUE: ${value}`);
iteratorResult = await iterator.next();
}
// And this would be the output:
//
// [Symbol.asyncIterator]() called
// next() called
// Creating a promise named promise-0 that will resolve in 0 seconds with a value of {"value":0,"done":false}...
// Promise promise-0 created
// (0 seconds later...)
// Resolving promise promise-0...
// Promise promise-0 resolved
// VALUE: 0
// next() called
// Creating a promise named promise-1 that will resolve in 1 seconds with a value of {"value":1,"done":false}...
// Promise promise-1 created
// (1 second later...)
// Resolving promise promise-1...
// Promise promise-1 resolved
// VALUE: 1
// next() called
// Creating a promise named promise-2 that will resolve in 2 seconds with a value of {"value":2,"done":false}...
// Promise promise-2 created
// (2 seconds later...)
// Resolving promise promise-2...
// Promise promise-2 resolved
// VALUE: 2
// next() called
// Creating a promise named promise-3 that will resolve in 3 seconds with a value of {"value":3,"done":false}...
// Promise promise-3 created
// (3 seconds later...)
// Resolving promise promise-3...
// Promise promise-3 resolved
// VALUE: 3
// next() called
// Creating a promise named promise-4 that will resolve in 4 seconds with a value of {"value":4,"done":true}...
// Promise promise-4 created
// (4 seconds later...)
// Resolving promise promise-4...
// Promise promise-4 resolved
Now let's say my initial wrong assumption about how this kind of for loop works was right. Then we should be able to replace the async iterable with a normal iterable, like so:
const iterable = {
[Symbol.iterator]() {
console.log('[Symbol.iterator]() called');
let i = 0;
return {
next() {
console.log('next() called');
const iteratorResult = {
value: makePromise(`promise-${i}`, i, i),
done: i > 3
};
i++;
return iteratorResult;
},
};
},
};
If you run any example with this last iterable you will notice no difference in the results. Not even in the times between one output and the next. But there is something you should notice: The done
property of the object returned by next()
is included inside the promise when using for await...of
. This is relevant in cases where deciding whether the for loop should stop iterating depends on the result of the promise.
For instance, let's say you have a REST api that has in one of the fields of the response json object a url to keep fetching the next results: You could still technically implement this with a normal iterator, but with a few caveats:
- First, the first time
next()
is evaluated you are forced to make suredone
is evaluated totrue
regardless of whether the REST api has actually any data or not to make sure at least the first request is made, otherwise no request would be made to begin with and you wouldn't have any way to tell if there is any data at all (if it evaluated to false, the request would still be done but the loop would end without any chance to do anything with the data and you won't be able to do anything about it). - Second, you will assume that the developer will always dutifully await each result in each iteration of the loop before reaching the next iteration. So, if a developer had the brilliant idea of not awaiting to make the requests run in parallel, then it would run an infinity loop that would only stop once the first promise finally resolves and updates the value of the
done
property for the first time. You can prevent this by fetching the data in the server implementing async iterators as I did in the examples (Notice how in thefor await...of
example I use[Symbol.asyncIterator]
instead of[Symbol.iterator]
) to force the developer to usefor await...of
and prevent these problems.
Of course. You are correct that the two loops share a similar purpose: they both iterate over an iterable containing promises and allow you to handle their resolved values. However, for await...of
provides cleaner and more concise syntax specifically designed to handle asynchronous iterables, while your first example is a more verbose way of achieving the same result with a regular for
loop.
Why use for await...of
?
The for await...of
loop is specifically useful when dealing with asynchronous iterables, which provide values over time, such as streams or generators that yield promises.
Example: Using an asynchronous generator
async function* asyncGenerator() {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
}
(async () => {
for await (const value of asyncGenerator()) {
console.log(value); // Logs 1, then 2, then 3
}
})();
This cannot be easily replicated with a for...of
loop because for...of
does not inherently understand asynchronous iterables.
And if you want to get the hood , go for the spec.
本文标签:
版权声明:本文标题:javascript - What is the difference between `for await` and awaiting a promise obtained from a synchronous iterable? - Stack Ove 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745626673a2667015.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
iterableWithPromises
? If you are talking about a synchronous iterator yielding promises, then you should use neither - see the thread linked by VLAZ. – Bergi Commented Nov 18, 2024 at 15:49for await
should be used when you need the result of the promise to know if you should keep iterating if you don't want to face the caveats I mentioned in my answer. – Adrian Commented Nov 18, 2024 at 15:58