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');
- So
console.log('start')
gets into the CallStack, executes and ride out of it. In the console, I get the output 'start'. - 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. - Then
console.log('two')
also gets into the CallStack, is executed and removed. In the output now we have (‘start’, ‘two’) - Exactly the same story happens with
setTimeout(() => console.log('three'), 3000);
- Again we repeat the same actions with
console.log('finish')
and in the output we have (‘start’, ’two’,’finish’) - It turns out that the Callback queue contains (‘one’, ’three’).
- 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?
CodePudding user response:
console.log('start');
setTimeout(() => console.log('one'), 0);
console.log('two');
setTimeout(() => console.log('three'), 3000);
console.log('finish');
Something like this happens:
- The JavaScript runtime starts, with your code supplied as an argument
- 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
'start'
is printed to the console- 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
) - Execution continues in the global execution context
'two'
is printed to the console- 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 - Execution continues in the global execution context
'finish'
is printed to the console- 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 - 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 - Anonymous function
() => console.log('one')
is executed and'one'
is printed to the console F1
is popped off the call stack- 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 - 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 - Anonymous function
() => console.log('three')
is executed and'three'
is printed to the console 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.