Software Engineering

Asynchronous JavaScript for Steady App Performance

As a developer, I naturally want my software to be reliable and responsive. In the early days of my career, feedback on my applications was mixed. Some apps scored high praise, but reviews were inconsistent on other apps because they would intermittently stop responding midsession—and we all know how little patience end users have for poor program responsiveness.

The underlying issue was that the apps were coded using purely synchronous JavaScript. Since JavaScript offers (seemingly) asynchronous functions, it’s easy to miss the fact that JavaScript’s runtime itself is synchronous by default, and this is a potential pitfall for developers. My curiosity drove me to investigate this programmatic puzzle.

The Problem: JavaScript Synchronous Blocking

I started my exploration by observing the way that regular, synchronous calls work, focusing my efforts on call stacks—last in, first out (LIFO) programming structures.

All call stacks function alike, regardless of the language: We push (add) function calls to the stack and then pop (remove) them as needed.

Let’s consider a short example:

function multiply(a, b) {
    return a * b;

function square(n) {
    return multiply(n, n);

function printSquare(n) {
    const squaredNum = square(n);


In our example, the outermost function, printSquare, calls the square function, which in turn calls multiply. Functions are added to our call stack in the order they are encountered. As each method is completed, it is removed from the end of the call stack (i.e., multiply would be removed first).

A column labeled call stack containing cells that are labeled (from bottom to top): printSquare(4), square(4), and multiply(4, 4).
JavaScript Call Stack Example

Since the call stack is synchronous, when one or more of these functions takes significant time to complete, the remaining tasks are blocked. Our program becomes unresponsive—at least temporarily—and resumes only when the blocked function is completed.

Common function calls resulting in these program delays include:

  • A while loop with a high iteration count (e.g., from one to one trillion).
  • A network request to an external web server.
  • An event that waits for a timer to complete.
  • Image processing.

For end users in a web browser, synchronous call blockages result in an inability to interact with page elements. And for developers, these stuck calls make the development console inaccessible and take away the ability to examine detailed debugging information.

The Solution: Asynchronous JavaScript Functionality

Asynchronous coding is a programming technique in which, after we invoke a function, the remainder of our code can run without having to wait for the initial function to return. When an asynchronous task completes, the JavaScript runtime passes the result to a function of our choosing. This method eliminates obstacles for our end users and developers.

JavaScript implements asynchronous functionality via a few key architectural components:

An animation showing the interaction and flow between the JavaScript call stack, browser API, and task queue that support asynchronous functions.
JavaScript’s Asynchronous Flow

Anything that needs to run asynchronously (e.g., a timer or external API call) is sent to the runtime engine’s browser API (web API). The browser API spawns a single execution thread per operation routed its way.

Each asynchronous JavaScript function call sent to the browser API has a corresponding promise that allows handler code to be triggered when the function completes (either successfully or unsuccessfully). When the function completes—regardless of whether it returns a value—its return fulfills its associated promise, and the function moves from the browser API into JavaScript’s task queue.

The key player in JavaScript’s asynchronous processing is its event loop. The event loop continuously checks if the call stack and task queue are empty, and coordinates when those completed asynchronous calls should be pushed back onto the main call stack.

Let’s now examine JavaScript’s setTimeout method to see JavaScript’s asynchronous method handling in action:

function a() {

function b() {
    setTimeout(() => {
        console.log("After 5 secs");
    }, 5000);

function c() {
    console.log("Hello World");

An animation showing a detailed flow from JavaScript’s call stack into the browser API and task queue for the preceding code example.
How the Browser API Handles the setTimeout’s Function

Let’s walk through the code:

  1. a goes to the call stack.
  2. b’s setTimeout invocation is moved to the browser API call stack.
  3. c goes to the call stack.
  4. c’s console.log call pushes onto the call stack.
  5. When the setTimeout method completes, it is moved from the browser API to the task queue.
  6. Any functions within the call stack process to completion.
  7. When the call stack empties, the event loop moves the setTimeout’s function from the task queue back into the call stack.

Software engineers can expand their development capabilities through the application of these JavaScript asynchronous methods. Now that we have seen how asynchronous methods within the JavaScript runtime are handled, I’ll demonstrate their applicability with a short example.

Real-world Applications: A Chatbot Example

I recently developed a browser-based chatbot. Synchronous behavior would have been undesirable as it would cause the conversation to appear disjointed and sluggish. My solution achieves well-paced conversation by asynchronously communicating with the ChatGPT external API to both send and receive messages.

To facilitate communication with the ChatGPT API, I created a simple Node.js server using OpenAI. Then I leveraged the asynchronous JavaScript fetch API that uses programmatic promises to provide a way to access and process responses:

  fetch('http://localhost:5000/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    body: JSON.stringify({
      query: 'What is the weather like in Seattle?'
  .then(response => response.json())
  .then(data => {

Our simple server asynchronously calls the ChatGPT service while providing bidirectional message transmission.

Another asynchronous method I commonly use is setInterval(). This function provides a built-in timer that subsequently calls a function repeatedly at any specified interval. Using setInterval, I added a typing effect to the user interface, letting the user know that the other party (the chatbot) is creating a response:

// Creating loader function for bot
function loader(element) {
    element.textContent = '';

    // 300 ms allows for real-time responsiveness indicating other-party typing
    loadInterval = setInterval(() => {
        element.textContent += '.';

        if (element.textContent === '....') {
            element.textContent = '';
    }, 300);

// Creating typing functionality
function typeText(element, text) {
    let index = 0;
    // 20 ms allows for real-time responsiveness to mimic chat typing
    let interval = setInterval(() => {
        if (index < text.length) {
            element.innerHTML += text.charAt(index);
        } else {
    }, 20);

These two asynchronous blocks turn an otherwise disjointed conversation into one in which participants feel engaged. But the responsiveness asynchronous JavaScript allows may be a less obvious key ingredient in other contexts.

More Asynchronous JavaScript Examples

Once I was tasked with creating a custom WordPress plugin that allowed users to upload large files asynchronously. I used an AJAX library to allow the user to upload their files in the background without having to wait for the page to reload. This allowed for a much smoother user experience and the application was a huge success.

In another use case, an e-commerce website was having trouble with slow loading times due to the large number of images it had to load. To speed up the process, I implemented an async JavaScript function (LazyLoading) to load each image asynchronously. This allowed the website to load faster, as the images weren’t all loaded at the same time.

I also worked on a project involving a money transfer application integrating various crypto and payment APIs. I needed to pull data from an external API, but the API took some time to respond. To ensure that the application didn’t grind to a halt while waiting for the API, I implemented an async function that was able to keep the application running while it waited for the API response, resulting in an enhanced user experience.

Asynchronous methods in a JavaScript implementation allow for powerful functionality in the service of end users, reducing UI slowdowns or freezes. That’s why asynchronous JavaScript is crucial to user retention in apps like Uber (running its booking and payment processes in the background), Twitter (loading the latest tweets in real time), and Dropbox (keeping users’ files synced and up to date across devices).

As a developer, you may worry that asynchronous JavaScript methods won’t appear on the call stack as expected—but rest assured, they do. You may confidently include asynchronous functionality among your options in delivering superior user experiences.

The Toptal Engineering Blog extends its gratitude to Muhammad Asim Bilal for reviewing the technical content and code samples presented in this article.