JavaScript Execution Order

Asynchronous Tasks in a Synchronous Language

Photo by The Creative Exchange on Unsplash

JavaScript is often described as a synchronous, single-threaded language. Essentially meaning that it can only perform one function at a time and if a function takes some time to finish, such as an HTTP request, it would literally block the user from interacting with the webpage. Click events would be delayed until the blocking function finally finishes, for example. Of course, this would be a bad user experience. So how exactly does JavaScript work around this? How is it able to perform asynchronous tasks? “Callback functions!” “Promises!” “Async!” Yup, you got it. I guess there is nothing else to say… All kidding aside, JavaScript, again, is a synchronous language. To understand how it can perform asynchronous tasks, we will have to take a peak at what is going on ‘under the hood’ of the browser .

The JavaScript engine has two main components — the heap which is used for memory allocation and the call stack which is our ‘single thread’. We will focus on the call stack and how it interacts with the other pieces of the execution order which include the web APIs, the task queue, and the event loop.

The call stack manages the execution of the functions being called using a last-in-first-out structure. Once a script is run, the first function is placed into the call stack. If the first function calls a second function, then that is stacked on top of it. Let’s say that the second function calls console.log('hello world');. This would be placed on top of the second function. If there is no other functions being called, the console.log executes and, once finished, is popped off the stack. Then the second function executes and is also popped off. Finally, the first one behaves the same way leaving the call stack to be empty. This is the single-threaded language in action.

But what happens if one of those functions took time to execute? Let’s say instead of the second function executing a console.log, it instead performed a setTimeout which took five seconds to complete. This would essentially prevent the rest of the functions in the call stack to execute until it has finished. This is called a blocking function and leads to a bad user experience. Here is where the rest of the execution order comes in. When a JavaScript runtime engine reaches an asynchronous function in the call stack, it will immediately return and pop out of the stack. While the call stack moves on to the next function, the web APIs handle the asynchronous function. It is because of the Web APIs built into our browsers that JavaScript is able to perform asynchronous tasks. The callback function and any metadata associated with it is registered to an event table and then passed to the task queue. For example, take the following:

setTimeout(function() {
console.log('Logged after a 1 second delay');
}, 1000);

A setTimeout accepts a callback function as its first argument and a number representing the delay time in milliseconds as its second argument. When it is popped off the stack and passed to the web api, the web api will set a timer based on the second argument and, once expired, passes the callback into the task queue.

The task queue operates on a first-in-first-out structure. The first function in must wait for the event loop’s permission to pass. The event loop is an algorithm constantly checking the call stack and will not let any of the callback functions into the stack until it is empty. Once it is, it will take the first callback out of the task queue and push it into the call stack allowing it to execute. Once the stack is empty again, the event loop will allow the next callback from the queue into the stack and repeat this process until the queue itself is empty.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store