How JavaScript works: debugging overview + 4 tips for async code

Ukpai Ugochi
SessionStack Blog
Published in
12 min readJul 1, 2021

This is post # 34 of the series, dedicated to exploring JavaScript and its building components. In the process of identifying and describing the core elements, we also share some rules of thumb we use when building SessionStack, a JavaScript tool for developers to identify, visualize, and reproduce web app bugs through pixel-perfect session replay.

Debugging plays a major role in the software development process and it’s an area where any developer can further improve on. Leaving crucial bugs in a production environment can cause huge losses for your application and company. The debugging process involves the identification and resolution of bugs in your code.

During the debugging process, you can step through your code to determine where each failure occurs and what is the cause of the error. All modern browsers have built-in JavaScript debuggers. Also, there are functions and flags in JavaScript that make debugging easier.

There’s a difference between debugging asynchronous and synchronous code in JavaScript. This is because, in asynchronous code, the timing of function invocations and their results aren’t sequential. Also, if there’s no response within a certain period, there’s a timeout that can act as a bug. In this article, we’ll explore how to debug JavaScript and tips for debugging asynchronous code.

Why is Debugging Important

Debugging is done mostly to avoid the incorrect operation of an application by fishing out bugs and incorrect code. Let’s look at some of the importance of debugging and why developers should meticulously and efficiently debug their applications.

Bugs can make your application crash. With proper debugging, you can spot bugs from your code and avert unexpected behavior.

For example, with debugging you can determine the cause of runtime or compile errors in code. Some code might not throw an exception or an error but may behave unexpectedly. For instance, assigning incorrect variables may cause unexpected behavior.

let a = 5;let b = 10;let c = 12;let d = a + b;// Developer was meaning to write let e = a + c;let e = b + c;

From the code above, the developer is expecting 17 as a response to unexpectedly get’s 22. If the developer debugs this code with the console.log() function, they can trace their error in the console. Sometimes, developers use new tools and programs for creating their applications which behave unexpectedly sometimes. With proper debugging, developers can determine how their application behaves overall.

How to Debug JavaScript Applications

JavaScript is used for most web-based applications and has started getting traction in mobile applications too. Developers must debug their applications before pushing them to the production environment.

Sometimes, developers pay more attention to writing code than debugging it. Let’s look at the processes for debugging a JavaScript application:

Identifying a Bug

Identification of bugs is the first step in debugging. For instance, engineers can either identify bugs after an error or an unexpected behavior during testing or by simply looking at their code. This step has to be repeated until the bug is identified. If bugs are not properly detected the application might act unexpectedly.

Debugging can be either done actively or passively. In passive debugging, the execution steps during debugging are followed by the developer. Afterward, the logs or the available data from the execution steps can be collected by the developer.

However, there are scenarios where the developer needs to communicate with the application during debugging. For instance, developers may need to send data into their application during debugging to see how the app behaves. This cannot be achieved in passive debugging since passive debugging only involves executing debugging steps and printing out data from the debugging process. Hence, developers use the active method of debugging. Active debugging allows developers to communicate with their applications during the debugging process.

Isolate the Bug

The process of isolating bugs is to determine what section of the program is causing the error. In this step, the developer tries to separate the portion of the code that causes the error from the rest of the application so that they can debug it thoroughly. This step generally follows any of these methods:

  1. Forward Analysis

In forward analysis, the engineers step through their code forward, sometimes placing breakpoints. Once there’s an abnormality recorded, the engineer focuses on the area to determine the cause of the error.

2. Backward Analysis

Sometimes, after forward analysis, developers need to trace an error back from the current breakpoint to an earlier breakpoint to find the cause of the error. This method is known as backward analysis.

Determine the Cause of the Bug

After isolating the bug, the next step is to debug the isolated section to determine its cause. For instance, if the error revolves around an incorrect input field, all input fields will be debugged to ascertain the cause of the bug.

Fixing the Bug

Once the source of the bug has been determined, the developer proceeds to fix the problem. If it turns out that it’s a deep problem, related to the architecture of the app, It is important that an experienced engineer is in charge of this process.

Test the Application

Debugging an application is an iterative process, it occurs from the start of the development process to the deployment of the application. After fixing a bug, engineers test the app to see if there are other abnormalities. Sometimes, new bugs may arise after fixing some parts of the app.

Debugging Methods in JavaScript

In this section, we’ll be discussing the different debugging methods in JavaScript and how to apply them in your application.

Web Console

Most web browsers have a web console that allows developers to log information associated with a web page. Since JavaScript can run on a web page, developers can debug their applications from the web console. One advantage of debugging with the web console is the ability to interact with a web page by executing JavaScript expressions relating to a page. You can open Firefox’s web console by pressing the CTRL + SHIFT + K buttons.

Let’s look at some of the debugging methods associated with the web console and how to debug a JavaScript application with them. The application we’ll be debugging is the program below:

  1. Log

To quickly debug code, developers log outputs in some portions of their code using the console.log() function. This function takes a parameter that can be an array, an object, Strings, or numbers. It is very useful for debugging because it returns the value of the parameter given as a log so that the developer can easily see what’s going on in their application.

For instance, from our sample application, we’d like to make sure that the button is working fine by ensuring that the id we are calling is the correct one. We can log a message when the id we are calling and our button’s id aren’t the same.

We won’t get any logs because we are calling the “demo” id which corresponds to our button’s id. Now, if the developer meant to assign the button’s id as “demi”, they can confirm that by assigning demi to correctId.

Now, we’ll get a log in our console saying: ID doesn’t match, id is demo. With this message, the developer knows that they assigned the wrong id to the button.

2. Warn

Sometimes, there are lots of logs in the console and it becomes difficult for the developer to spot a bug quickly. To make messages of interest distinct from other logs, the developer can make use of the console.warn() function. This function is very similar to the console.log() function, as it takes in parameters and returns the value of the parameters. However, it prints out logs as warnings.

If we implement the same example in our console.log() function, we’ll get a warning in our console:

3. Error

Sometimes, logs of errors get more attention and are easier to spot out. This is what the console.error() function does. It’s just like the console.log() and console.warn() function. If we repeat our example with the console.error() function, we’ll get an error:

4. Assert

The console.assert() function will write an error in your console if the assertion is false. It accepts parameters like the assertion and error message. To replicate our example with the assert method, let’s look at the example below:

5. Debugger Statement

In JavaScript, the debugger statement is used for invoking a debugger or stopping the execution of the code if there’s no debugger. This is used for setting breakpoints in your application. Setting breakpoints when debugging is a good practice since it allows the developer to inspect the program during its execution. If we modify our program by adding the debugger statement, our application execution will pause until we hit the play button in our browser:

Debugging JavaScript with a Debugger

Debugging tools are also known as debuggers.. They make it easier for developers to spot and fix bugs faster because they provide increased visibility and analysis. Let’s explore the Firefox debugger for JavaScript applications.

Firefox JavaScript Debugger

The Firefox JavaScript debugger allows you to debug your JavaScript application running locally and remotely. With this debugger, you can set breakpoints, events, Debug worker threads, etc. Let’s dive into debugging a JavaScript application with a Firefox debugger.

Start Debugger

First, we’ll open our application in the Firefox webpage. Next, we’ll start the debugger by clicking on CTRL + SHIFT + I and navigate to the debugger panel. Now that the debugger is open, let’s fetch our application’s file.

Notice that the debugger panel is divided into 3 parts, with the first part containing the necessary files for the web page, the second panel contains the file of interest (a workplace for the file we’ll be debugging) and the last panel containing tools necessary for debugging:

To open our file, click on the file:// folder in the first pane and choose the file of interest. Next, we’ll look at different steps to pause execution and take a closer look at our file.

Pausing Execution

Breakpoints pause code execution while debugging. In this section, we’ll explore the different ways to add breakpoints to our code when debugging in the Firefox debugger.

Unconditional Breakpoints

We’ll be using the console.error() example for this section. When you click on the line number of your file that you wish to apply a breakpoint on, you’ll automatically pause execution with an unconditional breakpoint. You can also right-click on the line number of your file and choose Add breakpoint.

Let’s apply an unconditional breakpoint on line 16; if (id === correctId) {, and click on the button in our web page. You’ll get the message “Paused at execution”:

Now, you can step through your code to inspect it. As you step through your code, notice that you’ll get the expected logs when you get to certain points. For example, at the end of line 19, you’ll get the error message. Also, notice that you can’t apply breakpoints on HTML code.

Conditional Breakpoints

To apply a conditional break, right-click on the line number of your file and choose Add condition. Conditional breaks only apply to your code when a defined expression evaluates to true. For example, adding the condition below on line 16: if (id === correctId) { will cause a break because id doesn’t have the same value as correctId.

id != correctId

If we change the condition to id === correctId, there won’t be a break.

You can remove breakpoints, edit conditions, etc. by right-clicking on the line number and selecting the option of your choice.

Event Listener Breakpoints

You can add event listeners to breakpoints or pause on exceptions. To add an even breakpoint to your code, expand the Event Listener Breakpoints section in the third panel and click on any event listener of your choice.

If you want to implement a pause whenever there’s an exception in your program, tick “Pause on Exceptions” under Breakpoints in the 3 panel.

Step Through Code

While your code is on pause, you can step through it to carefully observe for errors. You can step over, step in, or step out with the buttons in the 3-rd panel.

The step over the button, allows you to advance to the next line of your code in the same function. Step in allows you to advance to the next line of your code in a function. In a function call, step in allows you to enter the function being called and step through variables. The step-out button allows you to run to the end of a current function, skipping the return value from a function.

Tips for Debugging Asynchronous Code

Debugging synchronous code isn’t the same as debugging an asynchronous code in the context of JavaScript. Asynchronous code runs after the execution of the main thread to avoid freezing or blocking subsequent JavaScript code from running. In the process, where a timeout is set and no response is received, there can be an error.

Here are a few tips to keep in mind when debugging an asynchronous JavaScript code:

  1. When there’s a timeout error, it can be from your code or network connection. Start by increasing your timeouts and checking your internet connection.
  2. Since asynchronous code’s output isn’t sequential, tick the “Pause on Exception” button when debugging. This will pause execution whenever you get an exception. Use the step over button when you get an exception to inspect the entire call stack.
  3. The first step to debugging asynchronous code is to use the console.log() function in different code sections.
  4. Use the try and catch statements to handle errors properly in asynchronous code.

Conclusion

Debugging is an important step and should be incorporated with application development. It shouldn’t be left out as an afterthought. To make debugging easier, especially for bulky programs, it is a great step to use debuggers.

No matter the tools you use, there will be cases where the message of the error, its stack trace, and all of the information you have gathered are insufficient for you to properly reproduce and resolve the issue.

A solution like SessionStack will make the process much more efficient as it allows you to replay customer journeys as videos. This allows you to see how customers interacted with your web app and what happened on their screen when the issue occurred. Combining this visual information with all of the technical details from the browser, device, network, and environment will give you all of the context needed to reproduce the problem and resolve it efficiently.

There is a free trial if you’d like to give SessionStack a try.

SessionStack replaying a session

If you missed the previous chapters of the series, you can find them here:

--

--