Async Callstack and Property Debugging Sample
Test Message
Notes
  • If you click "Say Hello" or "Say Goodbye" you will see a message added below "Test Message". A timeout is then set and after 3 seconds, the app is supposed to clear that particular message. So, if you click "Say Hello" it will show a "Hello" message and then 3 seconds later only "Hello" messages should disappear. If you try clicking those buttons you will notice there is a bug, after 3 seconds it clears all messages, not just ones of the particular type.
  • So, let's debug that. You should open messages.js in the Debugger and put a breakpoint inside the App.Messages.ClearType method on line 18. Now, you can click on the "Say Hello" or "Say Goodbye" button. After 3 seconds, your breakpoint should be hit.
  • If you look at the callstack in any browser other than IE 11, you will see two parts to the callstack. You will see the ClearType function in messages.js and then you will see a line in jquery.js. Neither of these tells you what code asked to clear messages of a certain type. You can see in the debugger that the type is 3 and that is the bug. For "Say Hello" the type should be 1 and for "Say Goodbye" the type should be 2. But you can't find the code that calls this function. That is because this function call was queued up via setTimeout which is an async call. And the browser won't show you where setTimeout was called. If you could find that you could find the code that provides that type of 3 and you could fix that code.
  • In IE11 and Chrome, the debuggers have a feature that will let you find this call to setTimeout. For other browsers you would to resort to searching your source code for calls to setTimeout but even that might not be sufficient since there are other async methods, such as setInterval, ajax requests, promises, etc.
  • If you simply run this example in IE11 you will have already seen the callstack all way back to the setTimeout call in App.Messages.ClearLater on line 5 and you would see what function called ClearLater, it might have been sayHello or sayGoodbye depending on what button you clicked on.
  • In Chrome, you need to enable the "Async" checkbox above the call stack in the debugger and then you need to click the "Say Hello" or "Say Goodbye" button again and the next time you hit the breakpoint after enabling the "Async" checkbox you will also see the callstack that includes the call to setTimeout on line 5 of messages.js and what function called that, either sayHello or sayGoodbye.
  • Now, let's talk about debugging property access and modification
  • If you open the Developer console, you can type App.Counter to see the current value. You can use the up arrow to re-run that line a couple of times. If you do you will see that the value is changing pretty often. Now, the question is how do we find the code that is changing that value?
  • If App.Counter was a function we could find the source for that and put a breakpoint in it. But this is a property of App, so how can we debug it? If your browser supports ES5, which is IE9+, then you can use a new feature of javascript called getters/setters. This allows you to use Object.defineProperty to create a property. The feature of Object.defineProperty is that you can provide a function for setting the property and/or provide a function to get the value of the property. This feature is very similiar to properties in C#, you still access as them as properties (e.g. X.name = 'test'; X.name), but custom functions are run underneath the hood. So, we can write a custom getter and setter function and in the custom setter function use the debugger; statement. So, when code anywhere updates the property the browser will run our custom setter function and will then hit our debugger; statement which will cause the browser to stop the debugger on that line. We can then look at the call stack to see what code updated the property.
  • So, I've written a helper function to create this property, see the code below:
    function debugProp(obj, prop) {
        var value = obj[prop];
        var stopNext = true;
        Object.defineProperty(obj, prop, {
          get: function () {
            return value;
          },
          set: function (val) {
            value = val;
            if (stopNext) {
              stopNext = false;
              debugger;
            }
          }
        });
      };
  • This will only stop the debugger the first time the property is updated. All other property updates after that will just work without stopping the debugger. So, copy and paste that code in the console in order to create a global function caled debugProp.
  • Now, let's use this function and type debugProp(App, 'Counter') into the console. You will then see the debugger break and you will find the offending line of code. It was in utils.js as part of setInterval which means every 500 milliseconds it was updating App.Counter.
  • If we wanted we could make debugProp slightly more useful and stop only if the property was set to a certain value, so let's do that using the below function:
    function debugPropWithValue(obj, prop, valueToStop) {
        var value = obj[prop];
        Object.defineProperty(obj, prop, {
          get: function () {
            return value;
          },
          set: function (val) {
            value = val;
            if (val === valueToStop) {
              debugger;
            }
          }
        });
      };
    
  • So now let's type debugPropWithValue(App, 'Counter', 10) into the console. You will notice that the debugger is never stopped because the value of App.Counter never becomes 10. But we can update the value of App.Counter in the console using App.Counter = 0. If we do this, 5 seconds from now, the debugger for the browser will stop because the counter goes up twice per second.