imheretolearn1
imheretolearn1

Reputation: 137

How differently does our await keyword behave inside the for await of loop than in a normal async function?

Here's a simple async function:

async function f(){

    let data = await (new Promise(r => setTimeout(r, 1000, 200)));
    
    console.log(data)
     
    return data + 100;   
}

f()

console.log('me first');

Here's what's going to happen in this program:

  1. f() will be invoked
  2. It goes inside f execution context and await expression is run - since function call has higher priority than await keyword, our new Promise() function will be run first which will return a pending promise right now and after that await keyword will work.
  3. We set up a timer that will resolve our promise after 1 second
  4. since its not yet resolved, await will return us a pending promise and we go one layer out of our current execution context which happens to be global execution context where we start executing global codes.
  5. we hit "console.log('me first')" line and logs "me first"
  6. now after 1second, our promise has been resolved which means all the code followed by our await expression will be put inside a container and put into the microtask queue for the execution in the next tick! (Under the hood it probably calls .next() method just like a generator function but let's not go into that)
  7. Our sync code is done and there's one task into our microtask queue, we put that into our call stack to run!
  8. That runs and as soon as we go back into our async function, it starts from where it left i.e. the await expression.
  9. since our promise is now resolved, our await keyword will extract the value from that resolved promise and assign it to the variable d.
  10. We log that value into the next line.
  11. Finally, in the last line, we return the value + 100 from our async function which means the pending promise that we returned earlier (as mentioned in line 4) is resolved with that value i.e. 300!

So as you can see, I'm pretty comfortable with async await at this point! Now here's the code we have with for await of iteration!

    async function f(){

        
        let p1 = new Promise(function (r){
            setTimeout(r, 2000, 1)
        })
        let p2 = new Promise(function (r){
            setTimeout(r, 1000, 2)
        })
        let p3 = new Promise(function (r){
            setTimeout(r, 500, 3)
        })
     
        let aop = [p1, p2, p3];
        
        for await (let elem of aop ){
            
            let data = await elem;
            console.log(data)
        }
        
    }    

f()
    
    console.log('global');

Now intuitively, you would expect this to happen assuming the await will behave the same as it did inside our previous async function!

  1. f() is called
  2. We create three promises p1, p2, p3
  3. p1 will resolve after 2 second, p2 after 1 second and p3 after 500 m/s
  4. We put these promises inside an array called aop (because for of works with an object that has Symbol.iterator as the property and all array has that.)
  5. Now this is where things feel tricky to me!
  6. We'll iterate over each elements, for the first one, we go inside the loop and immediately await our first element, i.e. our first promise, p1.
  7. Since it's not resolved yet, it returns us a pending promise like it did in our previous example, OR DOES IT?
  8. The same will happen for other two promise, p2 and p3, more two pending promises are returned.
  9. Our async function is finished, we go one layer outside into our global execution to this line which prints global: console.log('global');
  10. At this point, our p3 promise has been resolved, since it was supposed to take only 500ms to finish, it resolved first! NOW THEN! Maybe whatever code we have left after that (console.log(data)) will be put into the microtask queue for later execution?
  11. By this logic, it should print 3 since our last promise resolved first, then 2 and then 1 but its printing 1, 2, 3! So what crucial points have I missed in my writing? for await is obviously parallel so it'll go thru each elements without waiting for the first one to finish, so why is it working like that ?

Upvotes: 2

Views: 338

Answers (2)

MinusFour
MinusFour

Reputation: 14423

I can't talk to the specific implementations of jobs in each engine (or even polyfills) but creating promises shouldn't push code into a separate stack. The await mechanism should do it.

Since we are talking about a regular iterable. The code shouldn't be that much different from:

console.log(await p1);
console.log(await p2);
console.log(await p3);

Precise semantics, are of course, not exactly the same. Order is still guaranteed by the iterator which is the main point. Even if p3 resolves first, the value is retrieved after it's awaited which happens after p2 and p1 are awaited.

Technically, each value that is a promise is being awaited with for await(... of ...) so, awaiting the value of the binding does nothing really (other than maybe pushing further code into the next stack).

E.g. you have console.log(await await p1);

Upvotes: 0

Benjamin Gruenbaum
Benjamin Gruenbaum

Reputation: 276506

For await loops have special behaviour when you get a promise.

Remember - promises are values - once you have a promise the action creating that promise (in this case setting up the timer) has already occurred. The promise is just the value+time for that action.

What happens here is actually:

  • You pass your array of promises (just results for already started actions) into for...await.
  • Since that array has no Symbol.asyncIterator - the loop checks if it has a Symbol.iterator. Since arrays are iterable and have that symbol - it is invoked.
  • The for... await loop checks for each returned value if it's a promise - if it is it waits for it before progressing the loop.

Since the values are promises - they will be ordered (that is, .next() will be called on the iterator returned from [p1, p2, p3][Symbol.iterator]() only after the previous promise returned from the previous .next call has settled.


Note:

  • You are using resolved but mean settled or fulfilled.

Upvotes: 1

Related Questions