How does Javascript really work? Javascript is a single-threaded language, so how do asynchronous operations occur? This article covers the call stack, event loop, callback queue, some APIs, and more.
The cover image is a simplified image of the Javascript runtime, which includes the heap where memory is allocated and the stack frames. In addition, there are Web APIs provided by the browser or other execution environments; such as DOM, AJAX, Timeout, but they are not directly part of the JS runtime.
We access Web APIs such as DOM, AJAX, and Timeout through the execution environment. Now, what is the callback queue in the diagram? Let's start from the beginning. Javascript is a single-threaded programming language, which means it has only one call stack. It can perform only one task at a time, as it is single-threaded. It can execute only one piece of code at a time. To better understand this, let's visualize it.
The call stack is simply a data structure that keeps track of where we are in the program. Now, here comes the point where things slow down. If we can execute only one piece of code at a time, we need to wait for the called functions to finish before the later functions can be executed. This may not be a problem if our code consists of only simple operations like console.log, but image processing or network requests can take a long time. Is this a problem? Definitely. Because we are running our code on the browser, and nothing we do on the browser will be processed as long as the stack is not empty, literally freezing the browser.
So, what is the solution? Asynchronous calls. Let's take a look at the following code snippet.
When we start running the code from top to bottom, take a look at how the setTimeout function behaves. It appears in the call stack and makes a request to the Web API before leaving the call stack. When the request is successfully completed, the Web API adds the function to the callback queue and the result waiting in the callback queue appears in the stack when the call stack is empty. This process is essentially the same in both node and browser environments, except that C++ API is used in node instead of WebAPI. In the above diagram, the setTimeout function is given a parameter of 500ms. But what if we set it to 0ms? No, it would not be executed immediately. It would be added to the callback queue (or task queue) and executed after the call stack is empty.
Let's take a look at some other examples of situations we may encounter.
setTimeout(function timeout(){ console.log("hi1"); },1000) setTimeout(function timeout(){ console.log("hi2"); },1000) setTimeout(function timeout(){ console.log("hi3"); },1000)
We have 3 setTimeout functions that can all be expected to run at the same time, i.e., 1 second later. However, these functions are added to the Web API and will not enter the callback queue until 1 second after they are added. Since queue elements will be added to the stack in order and the stack must be emptied first, we can be sure that they will run not 1 second later, but at least 1 second later.
Actually, asynchronous requests are queued before being executed, and we don't want this queue to grow indefinitely as it would slow down our program.
$.on("document", "scroll", dummy_function)
Let's imagine we have a code snippet like the one above, and every time we scroll, the dummy_function function will be called multiple times and added to the queue, causing an undesired situation called "flood". We can prevent this unwanted situation by ensuring that the request is made every few seconds.
References