Debugging Javascript — Part 1: Sources
This is Part 1 of a series that will explore debugging of a front-end Javascript application. Debugging can be reduced down to proper knowledge of the developer tools that each browser offers. In this article we will be focusing on Chrome developer tools and in particular how you can get the most out of the Sources tab, by focusing only on the most commonly used tools .
Intro
So you ’re reading the job requirements for a front-end position you ‘re going to apply for. Suddenly you stumble upon a bullet point saying “Exceptional debugging skills” and you think to yourself “yeah, i got this… don’t Ι?”
There are actually a lot of misconceptions about what debugging skills actually are. It’s often considered the art of finding out what’s wrong, regardless of the time it takes. You may even believe that it’s tied to the intelligence of a person, in the form of being able to stare at a bunch of lines and figure out what’s going wrong with the code. If this fails, then you give up and the console.log
and alert()
commands take place. Every blank line becomes a candidate for a console.log
in order to see the how the code executes. Most of the times it’s like that:
...
...console.log(input)
x = calculateX(input);
console.log(x);
if (something) {
console.log('inside');
} else {
console.log('outside');
}...
...
with the console output looking like that:
1.232
23232
outside
Seeing that, you might go “erm ok that’s expected. The problem must be somewhere else” and you begin to do the exact same thing at another part of your code, while simultaneously needing to rebuild your bundle (or refresh your page) for these changes to apply.
Finally, you figure out the issue and write a fix for that. You’ re like “perfect let’s push that ASAP so that the app stops crashing on production”. After having deployed, you suddenly see that you forgot to erase some of the debugging commands that you have added. If it’s a console.log
then that’s tolerable, but if it’s an alert
then you might have a problem. So you go and push another commit named “removed console.logs” or “removed unnecessary alert” to take care of that, while polluting your commit history.
Breakpoints
They are here to give you access to a set of tools that will replace any console.log
or alert
command you might have used in the past. These essentially “pause” the execution of the main Javascript thread powering your application, while allowing you to see a snapshot of the state that your application is in. Best thing is that no refresh or re-bundle is needed, so you can simply keep adding or removing breakpoints dynamically on runtime.
To add a breakpoint, you first need to open your developers tools (CMD+Shift+I on Mac and CTRL+Shift+I on Windows) and navigate to the Sources tab. This tab contains all of your application files and what you see is what Chrome “sees”. If a file there seems different than what you have in your Editor or IDE, then Chrome hasn’t yet received your code updates (probably due to caching). You can also manipulate your files directly in the Sources tab (should you wish to) and Chrome will be directly notified of your updates.
By pressing CMD+P (or CTRL+P on windows) you have access to a nice search bar that allows you to open the file that you are interested in debugging, similar to the way you’ d do it in your local IDE. Chrome orders the lines of the code in every file and by clicking on a line number you can add a breakpoint to this particular line (re-clicking it removes it). Whenever the main Javascript thread attempts to execute a “breakpointed” line of code, it will pause and wait.
If you only want to pause the thread under certain conditions, Chrome has an awesome feature that allows you to do that. This is useful in scenarios where your code will be passing through the “breakpointed” line a lot, but you only want it to “pause” on one occurrence of it. After you add a breakpoint you can “right-click” on it, select the option Edit Breakpoint and then type in an expression. The thread will now “pause” only when your expression evaluates to true and ignore the presence of the breakpoint whenever this expression is false. In the expression, you can make use of all variables that Chrome has calculated up to this point.
Let’s talk now of what you can do when the execution of your app has paused.
Variable Inspection
First things first, you can hover on every computed variable (variables that are above the “breakpointed” line) and see their current values. By pressing ESC, you can also toggle the console window which will allow you to run custom code using any of the computed variables; something extremely useful for custom checks and comparisons.
Variables that have been calculated in the same closure or not declared in this file at all but still utilised by it, can be found in the Scopes Section. This section is located in the right sidebar and it contains all the entities used by this file, grouped by the scope they belong to (local, global, closure etc.) with relation to the “pausing” breakpoint. For example, if the breakpoint is within a function, then everything that is declared inside this function is considered “local” and everything declared in another function that’s wrapping our current function is considered “closure” and so on. Take a look in this section and you will see everything that Chrome has calculated up to this point.
Should you wish to track the value changes of a handful of variables throughout the app’s execution, then the Watch Section might interest you. This is another nifty feature that replaces the need for a constant console.log
in order to get the latest value of some variable(s). By clicking on the “plus” icon (marked with a green circle in the screenshot below), an autocomplete searchbar will open, where you can type the name of the variable that you want to watch (regardless of the file that it exists in). Doing so, allows you to monitor its value throughout the your app’s execution. You could consider Watch Section as an extension to the Scopes Section, since it allows you to select the variables that you want to continuously monitor, instead of simply inspecting the values of the entities that this particular file (the one with the breakpoint) needed.
Important: The Watch Section doesn’t provide a live update, unless you inspect it between “pauses” (navigate between breakpoints). Thus, if the values of your watched variables might have changed without a pause occurring, then you will need to hit the “refresh” button (marked with a blue circle in the screenshot below) in order to get their latest value.
Call stack Inspection
You now have a way of knowing the value of every variable when your application is “paused”, but we haven’t answered the question of “how the hell did it get to this line?”. The Call Stack section of the sources tab, will give you everything you need to know about that. This contains a stack of the function calls and invocations that lead to the thread reaching the “breakpointed” line. Imagine it like a trail of events that lead to the current state. The lower you go in the stack the older the commands. Each stack entry “explains” why your code jumped to the stack entry right above it. This means that the code marked by the second-to-top stack entry was the reason the thread was forced to execute the code marked by the top entry.
To inspect this code, you click on an entry in the stack and it will automatically open the file and highlight the particular line. While there, you can also inspect the variables of each entry in the stack, at the very moment that the related code was invoked, as if you had an invisible debugger active on this line! Simply, click on a call stack entry and then either hover over a variable or check out the the entry’s Scopes Section.
Navigation options
Now that we went through what you can do while paused, let’s see what options you have in order to continue with the execution.
To fully understand the navigation options we will be using the following simple code that is currently “paused” through a breakpoint on line A.
function updateHeader() {
const name = getName(); // A
const x = 1; // D
}function getName() {
const name = `${app.first} ${app.last}`; // B
return name; // C
}
- The most common option would be to simply click on the “play” icon and everything will continue as normal until another breakpoint is encountered. This option is marked with a red circle in the screenshot above. In our example, supposing that no other breakpoint exists on lines B, C and D, then the code will never pause again.
- Another option would be to simply resume execution “line by line”. This is useful in situations where you are “paused” on a line of code containing a function that’s not relevant to the problem you’re debugging and you want to execute the function without stepping into it. By clicking on this option (marked with a blue circle in the screenshot above) you will resume the execution for whatever happens on the line and then the thread will “pause” again. In the code above, the thread (which is currently paused on A) will pause on D when this button is clicked.
- If the line does contain relevant code though, then you can step into it, by clicking on the button with the “arrow-down” icon (marked with a green circle in the screenshot above). This is useful in scenarios where further investigation is needed on how an output got calculated. In the example above, the thread (which is currently paused on A) will pause on B, C and D supposing you keep clicking the button.
- In cases where a particular function you’ re inspecting is too big, you can step out of it and pause again. This is useful in scenarios where you have already stepped into a function (with the previous navigation option) and you’ re like “ok, i’m done with that”. To understand that, suppose you are paused on A, then you click the navigation option № 3. This would pause the thread on B. If
getName
was too big of a function you could click “step out” and instead of going to C, you would instantly go to D. - If you got sick of all these navigation options, then good news! You can press on the “Deactivate breakpoints” switch (marked with a purple circle in the screenshot above) and you will disable all breakpoints until you press on it again. No matter how many more breakpoints you add, nothing will happen as long as this switch is active. This is very helpful in scenarios where you have a lot of breakpoints in different files and you don’t want to erase them, but simply de-activate them for a while, because you have been overran by “pauses” and no matter how many times you keep pressing the “play” button, there always is another breakpoint that “pauses” the app.
This is the perfect place to mention that all of your breakpoints will always be visible in the Breakpoints Section on the right sidebar. From there, you can enable, disable or remove them in a more fine-grained fashion. Just right-click on one of them and you’ll see all the available options. - Lastly, the most interesting switch of them all is the “pause on exceptions” button. This will add a breakpoint automatically whenever a Javascript exception occurs. This is helpful in scenarios where your code throws an exception, but you’ re not fully aware of what’s causing it. Having this switch active (marked with an orange circle in the screenshot above) can help you out identify the problematic piece of code.
These automatic breakpoints are in fact an interesting feature of the dev tools. You can tell the system to automatically add breakpoints whenever a request to a particular URL is sent (check out XHR/Fetch Breakpoints Section) or whenever a particular event occurs like clicks & hovers (check out Event Listener Breakpoints). The latter can be extremely helpful when debugging your logging mechanisms, but because this article series aims to familiarise people with debugging, we won’t be focusing on these advanced uses. Just thought we should mention them, so you know that these features exist.
Closing Notes
If you are a new front end developer, there is a high chance you might feel uncomfortable using some of the tools presented above, but if you spend some time familiarising yourself with them, you will see a lot of difference in the speed and easiness that you debug code.
In the next article we will be focusing on DOM inspection & manipulation, as well as DOM breakpoints.
Thanks for your time :)
P.S. 👋 Hi, I’m Aggelos! If you liked this, consider following me on twitter and sharing the story with your developer friends 😀