But now, after some digging into how stuff works, I think I understand what it is. I had heard of Chrome’s runtime V8, but I did not know what it really is. I had used callbacks, but did not know how they work. So this post tries to clarify the small misconceptions and incomplete information about these things.
- A runtime like Chrome’s V8, which has a heap and the call stack
- Web APIs provided by the browser, like
- A callback queue for events with callbacks, like
- and an event loop that does something we’ll look at later
What is the Call Stack?
So what is ‘blocking’?
Ever heard statements like nodejs uses an event-driven I/O bound non-blocking model that makes it perfect for data-intensive, real-time applications? Those terms are not very helpful yet. Let’s try to understand each term there.
- Event-driven: This is a programming paradigm in which the flow of the program is determined by events such as user actions (mouse clicks, key-press), or messages from other programs. For example: “When the user makes a GET request, render the page
index.html”. This is an event based trigger as we might say, where the event is the user sending a GET request and the trigger is the rendering of the page
- I/O bound: This refers to a condition where the time taken to complete a computation is determined primarily by the time period spent waiting for input/output operations to be completed. This is the opposite of a task being CPU bound, where the completion time is primarily determined by the time taken for the actual computation. So the rate at which the process progresses is limited by the speed of the I/O subsystem and not the CPU, hence it is good for data-intensive, real-time applications.
- Blocking: It is the condition when the call stack is occupied for long and the event loop is stuck because some function does not return until it has completed what it was doing, and it is taking a long time doing it. Since JS is singe threaded, a time taking operation, like making a network request blocks the subsequent code. The execution has to wait until the request is complete. This problem is avoidable (let’s look at that later).
So JS in the browser is a problem if it is blocking, isn’t it? Because say we make a network request, then we cannot click on things, submit forms, etc. because the browser is blocked now. But this does not happen! Why? Because we have asynchronous callbacks which solve this problem.
Is Concurrency a sham then?
Notice the arrows in the above image. The call stack can put things in the Web APIs, which push the callbacks into the callback queue once complete, and then comes the event loop magic. The event loop does the following:
The event loop keeps looking at the call stack and the callback queue, and does this simple job when it meets the condition above. There exists a tool where we can visualize this clearly. Loupe helps visualize the whole process beautifully. Go put some code there and see what’s happening. For example, we take the code below (on Loupe).
Though the visualization makes it clear, let’s go through what’s happening:
- Step into the
console.log('Hi');function, so it’s pushed onto the call stack
console.log('Hi');returns, so it is popped off the stack
- Step into the
setTimeoutfunction, so it’s pushed onto the call stack
setTimeoutis a part of the Web API, so the Web API handles that and sets a timer for 3 seconds
- The script continues, stepping into the
line 7, pushing it onto the stack
line 7returns, so it’s popped off
- The 3 second timer completes, so the callback
cback()moves to the callback queue
- The event loop checks if the call stack is empty. If it were not empty, it would wait. But because it is empty, the
cback()is pushed from the callback queue onto the call stack.
line 4is defined in
cback(), so it is pushed onto the stack and when it returns, it’s popped off the call stack.
The interesting thing to observe here is that
setTimeout with the second argument as
3000 doesn’t mean that the callback function will be called after 3 seconds. It means that it will be called whenever the call stack is empty after 3 seconds, which can also be never.
Try this to understand the above statement:
cback() never makes it to the call stack because of
blockage() and thus
setTimeout fails to give us the desired thing after 3 seconds. The browser has to render the UI every
16.67 milliseconds (60 frames per second), and if there is blockage in the stack, it will not be able to render. So blocking the event loop actually means having some function on the stack that does not return well in time.
This code on loupe gives the example of a more complex program, with an event handler defined for a button. The
$.on('button', 'click', ...) Web API keeps waiting for events (clicks on the button) (an example showing the event-driven nature), and pushes the said function in the callback queue when we click the button below. The event loop takes care of things thereafter.