javascriptasynchronousasynchronous-javascript

Do I get right how asynchrony works behind the scene in JS?


I have a question about asynchrony in JS. How it actually works. I'm not sure if I got it right, I hope you can help me figure it out.

So, I have a code:

console.log('start');
setTimeout(() => console.log('one'), 0);
console.log('two');
setTimeout(() => console.log('three'), 3000);
console.log('finish');
  1. So console.log('start') gets into the CallStack, executes and ride out of it. In the console, I get the output 'start'.
  2. Next, setTimeout(() => console.log('one'), 0) gets into CallStack, but since this is an asynchronous function from CallStack, it goes to the Web API and works there in the background. As soon as it completes, its callback function will ‘move’ to the Callback queue.
  3. Then console.log('two') also gets into the CallStack, is executed and removed. In the output now we have (‘start’, ‘two’)
  4. Exactly the same story happens with setTimeout(() => console.log('three'), 3000);
  5. Again we repeat the same actions with console.log('finish') and in the output we have (‘start’, ’two’,’finish’)
  6. It turns out that the Callback queue contains (‘one’, ’three’).
  7. From the Callback queue console.log('one') gets into the Call Stack, is executed and deleted. The output is (‘start’,’two’, ’finish’, ’one'), etc.

It turns out that event loop checks if the CallStack is empty, if it is empty and the Callback queue contains a callback function, then it pushes them into the CallStack and does it until the Callback queue is empty?


Solution

  • console.log('start');
    setTimeout(() => console.log('one'), 0);
    console.log('two');
    setTimeout(() => console.log('three'), 3000);
    console.log('finish');
    

    Something like this happens:

    1. The JavaScript runtime starts, with your code supplied as an argument
    2. The global execution context (stack frame) is configured and put on the call stack. Your code within the global execution context begins execution from the top
    3. 'start' is printed to the console
    4. Host (eg. browser or Node or Deno etc.) function setTimeout is called, supplying an anonymous callback function () => console.log('one') and a minimum wait period (0)
    5. Execution continues in the global execution context
    6. 'two' is printed to the console
    7. Host (eg. browser or Node or Deno etc.) function setTimeout is called, supplying an anonymous callback function () => console.log('three') and a wait period of three seconds
    8. Execution continues in the global execution context
    9. 'finish' is printed to the console
    10. Some time later, as close to 0 milliseconds after the first call to setTimeout as possible, a job is added to the macrotask job queue by the host environment
    11. The JavaScript runtime detects the presence of a job on the macrotask job queue, creates an execution context (stack frame) F1 for the running of the callback associated with it, and pushes it onto the call stack
    12. Anonymous function () => console.log('one') is executed and 'one' is printed to the console
    13. F1 is popped off the call stack
    14. Some time later, as close to 3 seconds after the second call to setTimeout as possible, a job is added to the macrotask job queue by the host environment
    15. The JavaScript runtime detects the presence of a job on the macrotask job queue, creates an execution context F2 for the running of the callback associated with it, and pushes it onto the call stack
    16. Anonymous function () => console.log('three') is executed and 'three' is printed to the console
    17. F2 is popped off the call stack

    Note that jobs can only be drained from the job queue when the one and only userland thread of execution completes whatever it is doing. Your code kindly leaves the JS runtime mostly idle while you wait for the setTimeout callbacks to be added to the job queue, meaning the callbacks are run in a timely fashion.

    Note that the host environment (eg. a browser), and the JS runtime, exist outside of the single thread of execution your JS code "sees". This means that the runtime and the host environment are able to keep track of things like pending setTimeout callbacks and job queues, without blocking your code.