How I debug Javascript code I didn’t write

Pranav Jindal
ITNEXT
Published in
4 min readSep 11, 2019

--

When debugging a code that you have not written, especially JavaScript code, things get real messy and convoluted. Thanks to Javascript’s single threaded approach and event-loop, the code doesn’t generally move in a straight direction. There can be async/sync callbacks, XHRs, timeouts/intervals, event listeners, promises and what-not, that “help” make your code NOT work in a straight forward way and it becomes very easy to get lost in finding what direction the code is moving.

At times, this gets very frustrating to find out God-knows-what-part of code changed some value to what it was not intended to be. I work on an Angular.js application which is fairly complex and has a lot of watches, event listeners, async calls that make it quite hard to follow.

Let’s say, we have got a bug where $scope.notifications.usage_exhausted was intended to be true, but somehow gets changed to false when you click a button and you have no idea what code did that. Generally, you will start with an approach to find out what that value does, what that button does, where are the places that value is set, what all input fields have set that property as ng-model (angular's ways of defining auto-sync model to an input field), etc. This way works but it can waste a lot of time looking at code that is not the culprit and making you feel what am I doing in life? What if I could just somehow watch changes to $scope.notifications.usage_exhausted when I click that darn button?

Well, you can exactly do that with following snippet:

What this snippet does is, it replaces that property on object with JavaScript’s native getters/setters that behave exactly like a normal property but with a debugger at your disposal whenever the property-you-want-to-watch changes. All you need to do is, watch the property when it was correct or not defined.

// watch when it was correct or undefined
watchProperty($scope.notifications, 'usage_exhausted');

Now, whenever $scope.notifications.usage_exhausted changes due to any code throughout the application, you’d get a breakpoint and all you need to see now is, the stack trace.

Auto debug point when the value changes
Previous step in call stack

And you exactly know where the value got changed. Had you been debugging the problem the normal way, you’d have had couple of breakpoints till now and still be debugging.

The scenario (button click) I mentioned is quite simple, but sometimes values change without any user action due to some delayed XHR calls, or timeouts/intervals, or anything async and that is when such cases become very difficult to pin-point the problem with traditional debugging approaches.

Watching Arrays

You can similarly watch changes to array indices too, to find what code sets at a particular index.

watchProperty($scope.projects, 3)

This will trigger a debug point whenever 3rd element on $scope.projects is set. Though this won’t help when 3rd element is pushed into the array. To mitigate this issue, we can create a new function to watch any changes on an array and can be coded as follows:

Here we hide the actual push, pop and splice methods of Array.prototype by creating these methods on the array itself. Now whenever, any part of code calls any of these mutating methods of that particular array, you’d have breakpoint and then again you can see the call stack to trace the problem.

You can create such debuggers for any type of code. Let’s say you use Sets in your application and you want to debug a particular instance of Set. Simply write another code snippet like watchSet (as we wrote watchArray above), to watch whatever function you’d like to watch.

You can add multiple watches or nested watches on objects for the properties that you are interested in. You can even add watchers while you are deep inside breakpoints for cases when the new value itself is an object and you have to watch a property on new value.

Caveats

This way of debugging is not flawless and has few limitations where such tricks don’t work, which are:

  • If a property is deleted using delete operator, this operation can not be detected and hence will go unnoticed.
  • If a property in your code is defined using Object.defineProperty / Object.defineProperties, that would cause the watcher to get overwritten and you’d have to watch it again.
  • Something I can not think of right now.

Frameworks like Vue.js, that themselves use getters and setters for change detection and reactivity have been taken care of in the watchProperty snippet by getting the original getter/setter using Object.getOwnPropertyDescriptor, saving them in the closure and calling them with correct this when getter/setters are called.

Helper Chrome Extension

Now creating these methods every time you want to debug is a problem, for which I use this awesome Chrome Extension which lets you define javascript code you want to run as soon as any website loads. Simply save these snippets for *.* wildcard so you have them at console anytime and anywhere. I use this extension for few more snippets like loading any javascript library on demand as and when needed. I’m a big fan of lodash and load it anytime I have to heavily modify data on the fly.

Using such watchers really speeds-up my debugging. I additionally use framework specific extensions (like this for angular.js) and new dev-tools features like conditional-breakpoints, logpoints and live expressions to make my debugging sessions less frustrating.

Happy debugging !!

--

--

JavaScript | VueJS | NodeJS | Typescript | AngularJS | Code Quality