javascript - What is the difference between `for await` and awaiting a promise obtained from a synchronous iterable? - Stack Ove

I know there are plenty of resources explaining for await...of, but I don't think I'll ever f

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
  • Related: Using for await...of with synchronous iterables – VLAZ Commented Nov 18, 2024 at 11:11
  • Does desugaring for await .. of answer your question? – Bergi Commented Nov 18, 2024 at 15:46
  • 1 What exactly is 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:49
  • @VLAZ That post might be related, but doesn't really showcase the thing that made me understand, which is the fact that for 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
  • @Bergi The post you mention answers my question, but to some extent: It does show an equivalent with more basic syntax (in the question itself instead of the answers), but it would be nice if it also mentioned the caveats I talked about in my answer since that is what REALLY made me understand this. – Adrian Commented Nov 18, 2024 at 16:03
 |  Show 2 more comments

2 Answers 2

Reset to default 4

No, 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 sure done is evaluated to true 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 the for await...of example I use [Symbol.asyncIterator] instead of [Symbol.iterator]) to force the developer to use for 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.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745626673a4636850.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信