JavaScript: The Power of Asynchronous Programming

Christian Baghai
4 min readApr 17, 2023

--

Photo by Pankaj Patel on Unsplash

Introduction

JavaScript has become one of the most ubiquitous programming languages today, yet it still faces criticism for its syntax and various approximations in its specifications. However, it would be oversimplifying the situation to attribute JavaScript’s enduring popularity to a mere fad or to the spread of bad programming practices. The true strength of JavaScript lies not in its prototype inheritance or other key concepts, but in its asynchronous functionality, which is often misunderstood.

JavaScript: Single-Threaded and Asynchronous

JavaScript was designed to operate in a single-threaded, asynchronous environment. A single-threaded JavaScript engine can interpret only one instruction at a time via its single execution stack (Call Stack). This simplicity is one of the language’s main advantages.

Asynchronous processing in JavaScript allows certain tasks to be delegated outside the engine, preventing a JavaScript application from blocking the browser during execution. This is especially important for web pages, where the browser is responsible for rendering the user interface (UI), handling input/output (IO), and managing other tasks.

The Asynchronous Circuit

The asynchronous functionality in JavaScript relies on a circuit that consists of the execution stack, host system APIs (such as those provided by a browser or NodeJS), the callback queue, and the event loop. The only component of the JavaScript engine directly involved in the asynchronous circuit is the execution stack. The host system APIs, callback queue, and event loop are not strictly part of the JavaScript engine, making JavaScript more than just a language — it is also an architecture.

Circuit Components of the Asynchronous

Execution Stack

The execution stack keeps track of function calls and their execution context, such as local variables and call parameters. When a function calls another function (or itself), the execution stack manages the call chain. If a function exceeds the capacity of the stack due to poorly implemented recursive calls, a “stack overflow” error may occur.

Host System APIs

The host system (e.g., a browser or NodeJS) provides various functions and objects (APIs) that allow the JavaScript engine to interact with the host or operating system. Some of these functions are asynchronous, such as XMLHttpRequest.send() and FileReader.readFile() for resource access, or the DOM event listener for event management.

Callback functions are used to determine when asynchronous processing is complete and to handle the results of that processing. In JavaScript, callbacks are often anonymous functions passed as parameters and defined at the moment of the asynchronous function call.

Event Queue

When asynchronous processing is complete or a specific event occurs, the provided callback is placed in the callback queue before being processed by the event loop.

Event Loop

The event loop monitors the state of the execution stack. If the stack is empty, the event loop transfers the waiting callback from the queue to the execution stack, allowing the callback to be executed.

Understanding the Asynchronous Procedure

To illustrate the involvement of the different components in an asynchronous call, consider the following example, which uses the standard asynchronous function setTimeout():

var k = 0; 

function getData(callback) {
console.log('getData()');

setTimeout(function setTimeoutCB(counter) {
console.log('setTimeoutCB()');
callback(null, counter);
}, 500, ++k);
}

getData(function getDataCB(error, data) {
console.log('getDataCB()');

console.log('data: ', data);
});

In this example, the setTimeout() function calls a callback function named setTimeoutCB(), which in turn calls the callback() function provided as a parameter to getData(). When getData() is called, a getDataCB() callback function is also provided as a parameter.

The asynchronous procedure can be broken down into the following steps:

  1. The getData() and setTimeout() functions are pushed onto the execution stack. The setTimeout() function, which is part of the browser’s API, is then called and handled asynchronously by the browser. At this point, the execution stack is empty.
  2. The delay countdown takes place in a separate thread. When the delay has elapsed, the callback function (setTimeoutCB()) is placed in the callback queue. The parameters for setTimeoutCB() are provided by setTimeout(). In other asynchronous functions, these parameters could include error messages or binary data.
  3. The event loop detects the presence of setTimeoutCB() in the queue and ensures the execution stack is empty. If so, the event loop sends the callback to the execution stack, where it is executed.

The subsequent stacking of the callback() function (a parameter of getData() referring to getDataCB()) on top of setTimeoutCB() is not shown in this example, but it is important to note that setTimeoutCB() still has access to the callback parameter due to the concept of closures in JavaScript. The setTimeoutCB() function is not asynchronous like setTimeout(); it remains under the full responsibility of the JavaScript engine and does not require browser API calls, queuing, or event loop intervention.

It may be helpful to review the code, diagrams, and explanations multiple times to fully understand the asynchronous process.

Conclusion

The asynchronous mechanisms in JavaScript may not be overly complex, but they can be challenging to explain. Despite the limited resources available online, understanding the inner workings of asynchronous functions and callbacks is crucial for JavaScript developers.

Philip Roberts has created an online tool that dynamically visualizes the flow of asynchronous code, which can be helpful for those looking to deepen their understanding. In his video presentation, he explains the asynchronous process in greater detail and provides the inspiration for much of the content in this article.

By mastering the asynchronous capabilities of JavaScript, developers can harness the true power of the language and create efficient, responsive applications that thrive in today’s fast-paced digital landscape.

--

--