3

As we may know, var keyword defines a variable globally, or locally to an entire function regardless of block scope. So the below code will log 5 times with the same value.

for(var i = 0; i < 5; i++){
  setTimeout(() => console.log(i), 2000);
}

To visualize the above JS runtime like this enter image description here As you can see, 5 tasks in Callback Queue will wait until Call stack is empty. So after the synchronous loop is done - It means Call stack is empty in my case, then 5 scheduled tasks - console.log(i) with the value of i is equal to 5 will be executed. You can play around here

And what I want is to log right after i == 2. It works as I expected.

var sleep = (ms) => new Promise(resolve => setTimeout(() => resolve(1), ms));
async function runAsync(){
  for(var i = 0; i < 5; i++){
    if(i == 2) await sleep(2000);
    setTimeout(() => console.log(i));
  }
}

runAsync();

But I'm curious how it works while I'm not sure the call stack is empty? Whether when I'm using await, allowing the caller of the async function to resume execution, or some else?

Any explanation/visualization of image flow would be appreciated. Thanks.

Nguyễn Văn Phong
  • 13,506
  • 17
  • 39
  • 56
  • 1
    It works like this https://stackoverflow.com/questions/30899612/explanation-of-let-and-block-scoping-with-for-loops But I'm assuming that we are using `var` combined with `await` keyword. – Nguyễn Văn Phong Apr 23 '21 at 02:50
  • "*after the loop is done, 5 tasks will be executed*" - that's pretty confusing actually. The animation of the code execution is still running when they are put in the callback queue, but in reality the loop finishes, then it waits for 5 seconds, and only *then* the handlers get scheduled. Try with a longer timeout (around 20s) to see what is actually happening. – Bergi Apr 23 '21 at 12:45
  • Don't get me wrong. I said `5 tasks in Callback Queue will wait until Call stack is empty`. So although longer timeout it still waits the loop finish then execute the scheduled tasks with the same value `i = 5` thanks to `event loop` – Nguyễn Văn Phong Apr 23 '21 at 13:01
  • 1
    I feel like the settimeout + var + async/await is complicating the question. If this really is a dive into how the evenloop works rather than understanding why we don't use `var` in loops, there's probably a better example of code to explore – Kevin B Apr 26 '21 at 15:16

1 Answers1

5

await cedes control of the thread and allows other processes to run until the promise being awaited resolves. Even if the promise is already resolved, await will yield to any "microtasks" that have been waiting to execute, but that's a moot point in your case because your promise takes a full two seconds to resolve.

In your case, two setTimeouts are queued up before the await, so they are allowed to run when the await happens.

The timeline is basically like this:

  • i = 0
  • setTimeout 1 scheduled
  • i = 1
  • setTimeout 2 scheduled
  • i = 2
  • await
  • setTimeout 1 callback runs
  • setTimeout 2 callback runs
  • setTimeout 3 scheduled
  • i = 3
  • setTimeout 4 scheduled
  • i = 4
  • setTimeout 5 scheduled
  • i = 5
  • loop ends
  • setTimeout 3 callback runs
  • setTimeout 4 callback runs
  • setTimeout 5 callback runs

You can see that i is 2 when the first pair of setTimeouts are allowed to execute, and it is 5 when the remaining 3 execute.

Here is a snippet that hopefully demonstrates the process a little better:

var sleep = (ms) => new Promise(resolve => setTimeout(() => resolve(1), ms));
async function runAsync() {
  for (var i = 0; i < 5; i++) {
    console.log('i is now', i);
    if (i == 2) {
      console.log('about to sleep');
      await sleep(5000);
      console.log('now done sleeping');
    }
    console.log('about to setTimeout. i is', i, 'right now');
    setTimeout(() => {
        console.log('setTimeout task running:', i, '- scheduling a new timeout.');
        setTimeout(() => console.log('inner timeout:', i), 1000);
    });
  }
  console.log('done looping. i is', i);
}

runAsync();
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • 1
    Thanks for your answer. You mean `await` will make sure that all scheduled tasks will be run? – Nguyễn Văn Phong Apr 23 '21 at 03:08
  • 1
    @NguyễnVănPhong I'm not sure if it ensures that _all_ scheduled tasks will be run, but in your example that is definitely the case because you are `await`ing for a full 2 seconds so that is plenty of time for all of the scheduled tasks to fire. – JLRishe Apr 23 '21 at 03:11
  • 1
    @NguyễnVănPhong I can see that you've placed a bounty on your question. Is there something that's still unclear to you? – JLRishe Apr 26 '21 at 13:28
  • Yes. As you can see the animation image, the scheduled tasks in `Callback Queue` will be run whenever the `Call Stack` is empty thanks to `event loop`. Furthermore, Javascript is single thread. So I'm curious about the `Call Stack` when `i = 2`, I don't think it's empty. So where the loop in place? How 2 scheduled tasks run? I would appreciate if you visualize How `call stack, web api, callback queue` look like. What happens if there are some Async tasks in web api? Whether Just some scheduled tasks in `callback queue' run? Thanks, sir. – Nguyễn Văn Phong Apr 26 '21 at 15:03
  • None of that changes how the event-loop functions. – Kevin B Apr 26 '21 at 15:52
  • 1
    @NguyễnVănPhong As I've explained, `await` "pauses" the function to allow other tasks to run, so yes, the call stack _is_ cleared when `i = 2` because the active call stack up to that point is basically pushed aside to wait until the awaited promise resolves. When it resolves, a task is scheduled to resume the function where it left off. _"What happens if there are some Async tasks in web api?"_ Exactly the same thing happens. `await` behaves the same no matter where the tasks are coming from. – JLRishe Apr 26 '21 at 17:15
  • I nearly got you. Hopefully, you can give me an image/text or useful docs to describe the following concerns: 1) How JS know exactly the `i ==3` to turn back the `CallStack` to continue running? AFAIK, C# has something called `state machine`. 2) Because we wait `sleep` 2s so all scheduled tasks (maybe the previous or any task moved on to `Callback queue`) will be run until the awaited promise resolves, right? – Nguyễn Văn Phong Apr 29 '21 at 23:58
  • 1
    "Even if the promise is already resolved, await will yield to other processes that have been waiting to execute" could you please clarify this point? Awaiting an already resolved promise will only queue a new microtask, so only already queued microtasks could get executed before, no other tasks or event callback. `(async () => { const p = Promise.resolve(); let i = 0; setTimeout(() => console.log( i ), 0 ); for(;i<1e7;i++) { await p; } console.log("done"); })()` will always output "done" then "10000000" and the browser will not even have a chance to update the rendering during this loop. – Kaiido Apr 30 '21 at 02:16
  • @NguyễnVănPhong 1) TBH I do not know the deep underlying details (which I suspect may be an implementation detail), but basically, the function's execution is "frozen in time" while it waits for the promise. When the promise resolves, it continues where it left, and note: that happens _before_ `i` is incremented to 3. The `i++` happens after the await finishes. 2) Yes, the JS engine will be free to execute any waiting and new tasks until the function resumes 2 seconds after it starts waiting. I'm sorry, but I'm not very good at writing diagrams, but I will provide some sample code. – JLRishe Apr 30 '21 at 18:09
  • @Kaiido Ok, fair point. I will amend my answer. – JLRishe Apr 30 '21 at 18:09
  • Thanks in advance. I would appreciate with your support and happy to award bounty to you when you're done ^^! – Nguyễn Văn Phong Apr 30 '21 at 21:13
  • @NguyễnVănPhong Please check out the code sample above. I think it gives a pretty good sense of what's happening. – JLRishe May 01 '21 at 04:32
  • Noted with many thanks. Let me check then get back to you soon – Nguyễn Văn Phong May 01 '21 at 04:33
  • That extremely makes sense. I really like the `inner timeout` to answer my concern about other scheduled tasks. – Nguyễn Văn Phong May 01 '21 at 04:40