In the picture below we can see an overview of the elements involved in the JS execution in the browser. The V8 Engine contains a call stack. The engine communicates with the web APIs, and there is also a queue and a mysterious event loop. In the following sections, we are going to describe in more details this theoretical overview (If you are running JS on the back-end, the idea is pretty much the same, but instead of web APIs, you have NodeJS APIs).
The call stack is a mechanism so the interpreter knows its place in a script. When a script calls a function, the interpreter adds it on the top of the call stack. When the current function is finished, the interpreter takes it off the stack (Last In, First Out).
Consider this simple JS function:
Let’s see the call stack in action when this code is executed.
1. The interpreter starts calling
foobar is added to the stack.
3. The interpreter steps into the function, and
console.log(‘A’) is called and added to the top of the call stack.
console.log(‘A’) is executed, and the letter “A” is printed in the console.
console.log(‘A’) is removed from the call stack since its execution has been completed, and the interpreter moves on to the next command.
console.log(‘B’) is processed the same way as the previous one, and the letter 'B' is printed in the console.
console.log(‘B’) is removed from the stack, and the interpreter moves on.
console.log(‘C’) is processed the same way as the previous ones, and the letter 'C' is printed in the console.
console.log(‘C’) is removed from the stack, and the end of the
foobar function is reached.
foobar is also removed from the call stack, which becomes empty again.
Cool! But what if we have a very slow operation, such as image processing or network requests? Since JS can do only one thing at a time, this slow operation will block all other operations until it is finished. During this time, the user won’t be able to interact with the page; the browser will simply freeze. This is not ideal, and this is not what actually happens.
1. Now, instead of calling
console.log(‘B’) directly, we are putting it inside a callback function that will be called later. The
setTimeout is an asynchronous function that we are using to simulate a slow operation. Here again, the interpreter starts by calling
2. The first steps are the same:
foobar is added to the call stack, the interpreter steps in, adds
console.log(‘A’) to the stack, prints the letter 'A,' and removes the call from the stack. Then the interpreter reaches the
setTimeout function and adds it to the call stack.
3. As we noted in the beginning of this article,
setTimeout is provided for us by the Web API. So it receives the
setTimeout call alongside the callback and starts processing it.
4. Now the call to
setTimeout itself is completed, so it can be removed from the stack. The Web API continues the processing, but the call stack is not blocked anymore, so the interpreter can move to the next command.
5. This next step is executed normally, and the letter 'C' is printed to the console.
console.log(‘C’) is removed from the call stack, and so is
foobar since the interpreter has reached the end of the function. The call stack is empty again.
7. Ok… we are almost there, but what about the letter “B”? How is the callback sent to the call stack so it can be executed? When an asynchronous call is completed, the callback function is pushed to the Task Queue.
8. This is where the last piece of the puzzle comes in--the event loop. The event loop has one simple job: it looks at the call stack and the task queue, and if the stack is empty, it takes the first item in the queue and sends it back to the call stack.
9. Finally, the callback is executed, the letter "B" is printed in the console, and the callback is removed from the call stack.
And that’s it! If you want to dive a little deeper into the concepts presented above, be sure to check the links below.
Rafael Bicalho is a UI Engineer at Avenue Code. He is a fan of technology, coffee and programming.