Oh JS Async, How I Love You

Concurrency happens when multiple computer computations are happening simultaneously at the same time. Javascript runs via a call stack that is a parsed sequentially line by line (single threaded synchronous). If delays occur in the code execution whether intentional or non-intentional, how do you prevent them from blocking your code execution?

Even though the call stack in Javascript always gets parsed line by line in a single thread, it can can process Timer functions in setTimeout (for request delays in code execution) that work asynchronously (concurrent thread processing) on browser web APIs. The event loop has two types of tasks: microtasks (job tasks like promises) and macrotasks (the callback queue or event queue). The job tasks inside the event loop also includes the render steps (like requestAnimationFrame(rAF), css style calculation, layout, and painting the actual pixel data). The microtask queue in the event loop handles promises and will always be prioritized before the task queue and render steps. Every loop in the event loop is one tick. The job tasks like promises are executed first. Because promises can reference promises that can reference promises, there is a variable called processMaxTick to limit job tasks to 1000 jobs. The job tasks always run first in the first tick in the event loop up to the processMaxTick limit, and then on the next tick the macrotasks including the callback queue tasks are run in the event loop.

Calling setTimeout triggers the execution of the web API, which first processes job tasks and adds the callback to the callback queue. The event loop then takes the callback from the callback queue and adds it to the stack as soon as it’s empty. The event loop is the secret mechanism behind Javascript’s asynchronous programming because it handles all of the concurrency in JS.

Whenever an asynchronous function is called, it will follow instructions to send it to a browser web API. Unlike the call stack, the callback queue follows the FIFO order (First In, First Out), meaning that the calls are processed in the same order they’ve been added to the queue. As mentioned above, the event loop handIes macrotask queues, microtask queues (job tasks), and render list operations. The task queues include callback queues or event queues; in addition, web APIs can add to the task queues. In setTimeout, when you pass in a callback function and 1000ms timer, the web API will set-up the timing operation to be calculated in a macrotask callback queue in the event loop. Eventually, the invoked callback task reaches the front of the callback macrotask queue and is picked up by the event loop and is executed in the call stack. The event loop constantly checks to see if the call stack is empty. Empty as in all of the synchronized code functions have been popped out (removed) from the call stack. Whenever the call stack is empty, the event loop will check the macrotask callback queue for any waiting messages, starting from the oldest message. Once it finds one, it will add it to the stack, which will execute the function in the message.

The callback we passed as an argument to setTimeout is written in JavaScript. Thus, the JavaScript interpreter needs to run the code, which means that it needs to use the call stack, which again means that we have to wait until the call stack is empty in order to execute the callback. For more about the JS callstack, check out my other blog post. Lot going on under the hood, isn’t there?

Three super cool JS features (callback functions, promises and async/await) allow it to run asynchronously with third party browser web APIs including XHR (via fetch), Timer (via setTimeout), and DOM (via document).

Callback functions are not asynchronous by nature, but they play a very vital role in allowing asynchronous features in Javascript.

With the features of web APIs, we’re now able to do things concurrently outside of the JavaScript interpreter. But what happens if we want our JavaScript code to react to the result of a Web API, like an AJAX request, for instance? That’s where callbacks come into play. Through them, web APIs allow us to run code after the execution of the API call has finished.

The Javascript ecosystem works beautifully with Django-like single page applications (SPA) which are web applications where everything happens on one page. The SPA apps are built fully on the client side, so for any action the user performs, there will be immediate feedback. AJAX (Asynchronous Javascript and XML) allows particular parts of a single page application to load without doing a full page refresh. Possible again through asynchronization. If we want to change the page asynchronously, all we have to do is fetch the innerHTML of  #content from the server.

A Promise is an object that handles the eventual success or eventual failure of async operations like requesting data from the fetch XHR API. Promises were created to solve the famous callback hell problem but introduce complexities on its own, and specifically syntax complexity. Promises has three states: pending (the initial stage), resolved (then works when a promise is resolve), or rejected (catch works when a promise is rejected). By avoiding callback hell, promises create DRYer code than callback functions alone like setTimeout.

Async/await is a combination of promises and generators and the next evolution that creates even DRYer code than promises. I go more into async/await in this another blog post.

Our programs are not getting stuck when an element of our code block takes forever to load, and that’s why I love asynchronization.