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:
- f() will be invoked
- 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.
- We set up a timer that will resolve our promise after 1 second
- 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.
- we hit "console.log('me first')" line and logs "me first"
- 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)
- Our sync code is done and there's one task into our microtask queue, we put that into our call stack to run!
- That runs and as soon as we go back into our async function, it starts from where it left i.e. the await expression.
- since our promise is now resolved, our await keyword will extract the value from that resolved promise and assign it to the variable d.
- We log that value into the next line.
- 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!
- f() is called
- We create three promises p1, p2, p3
- p1 will resolve after 2 second, p2 after 1 second and p3 after 500 m/s
- 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.)
- Now this is where things feel tricky to me!
- 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.
- Since it's not resolved yet, it returns us a pending promise like it did in our previous example, OR DOES IT?
- The same will happen for other two promise, p2 and p3, more two pending promises are returned.
- Our async function is finished, we go one layer outside into our global execution to this line which prints global: console.log('global');
- 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?
- 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 ?