I am using console.log() and console.dir() statements in 3 places in my 780 lines of code JS script. But all of them are useful for debugging and discovering problems that might appear when using the application.
I have a function that prints internal application's state i.e current value of variables:
printData: function () {
var props = {
operation: this.operation,
operand: this.operand,
operandStr: this.operandStr,
memory: this.memory,
result: this.result,
digitsField: this.digitsField,
dgField: this.dgField,
operationField: this.operationField,
opField: this.opField
};
console.dir(props);
}
I also have a list of immutable "contants" which are hidden with closure but I can print them with accessor method called list(); in console.
Something like this:
list: function () {
var index = 0,
newStr = "",
constant = '';
for (constant in constants) {
if (constants.hasOwnProperty(constant)) {
index = constant.indexOf('_');
newStr = constant.substr(index + 1);
console.log(newStr + ": " + constants[constant]);
}
}
}
The third place where I use console in debugging purposes is in my init(); function, where I print exception error if it happens.
init: function (config) {
try {
this.memoryLabelField = global.getElementById(MEMORY_LABEL);
this.digitsField = global.getElementById(DIGITS_FIELD);
this.digitsField.value = '0';
this.operationField = global.getElementById(OPERATION_FIELD);
this.operationField.value = '';
return this;
} catch (error) {
console.log(error.message);
return error.message;
}
}
As my question states, should I keep these console statements in production code?
But they come very useful for later maintenance of the code.
Let me know your thoughts.
Since these are not persistent logs you won't get much benefit out of them. Also it runs on every individuals machine since everyone has their own copy of the program. If you really need it, it would be better to have a variable that can toggle this feature. Espcially if you are targetting to debug a whole lot of pre-determined stuffs.
Client Side issues needs to be debugged slightly different from Server Side. Everyone has their own copy of the program. Browser-JS is run on client side you open the browser and all code that you wrote is with you in a full blown repl and debugging is easy compared to Server side where most likely you wouldn't have access to that system. It is so flexible that you could just override it when you are checking something in production right from your own browser without affecting anyone. Just override it with those console statements and debug the issue.
Its a good idea to have logs in Server side programming. It gives a lot of useful information and is persistent.
As said above, debugging code should not be present on a production environment.
However, if you plan to keep it anyway, keep in mind that all browsers do not support it.
So you may check its availability and provide a fallback:
if (typeof window.console === 'undefined')
{
window.console = {};
}
if (typeof window.console.log === 'undefined')
{
window.console.log = function() { };
}
According to mozilla developer network
This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.
Leaving console.log() on production sites can cause issues since it is not supported by older browsers. It is most likely to throw exceptions in that case.
It's historically considered a bad-practice by Javascript programmers. At the start of the eons, some browsers didn't have support for it (IE8 or less for example). Other thing to take in mind is that you are making your code larger to download.
Related
I have a page that contains many script components (50+) and I am getting an error when using IE at some random instance (doesn't happen in Chrome or Firefox).
"Out of Memory at line: 1"
I've done some google search too and that reveals issues with IE handling things differently to Chrome and FF. I would like to catch this error and know exactly what the cause of that script error is.
What would be the best way to use a global try-catch block on that many script components? All these script components are on the same page. Looking forward to your suggestions.
You might want to try window.onerror as a starting point. It will need to be added before the <script> tags that load the components.
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror
If that fails, you might try reducing the components loaded by half until the error no longer occurs. Then, profile the page (you may have to reduce further due to the demand of profiling). Look for a memory leak as #Bergi suggested. If there is in fact a leak, it will likely occur in all browsers, so you can trouble-shoot in Chrome, as well.
If that still fails to yield anything interesting, the issue may be in one particular component that was not in the set of components you were loading. Ideally, anytime that component is included you see the issue. You could repeatedly bisect the loaded components until you isolate the culprit.
Finally, forgot to mention, your home-base for all of this should be the browser's developer tools, e.g. Chrome dev tools, or if it is unique to Edge, Edge debugger.
And FYI, Edge is the browser that crashes, but that does not mean the issue is not present in Chrome or FF.
One important thing that is missing in your question is if the error happens during the page loading or initialization or if it happens after some time while you browse the page.
If it's during loading or initialization, it's probably caused by the fact that your page contains too many components and uses much more memory than the browser is willing to accept (and IE is simply the first one to give up).
In such case there is no helping but reduce the page size. One possible way is to create only objects (components) that are currently visible (in viewport) and as soon as they get out of the viewport remove them from JS and DOM again (replacing the with empty DIVs sized to the size of the components).
In case the error happens while browsing the page, it may be caused by a memory leak. You may use Process Explorer to watch the memory used by your browser and check if the memory constantly increase - which would indicate the memory leak.
Memory leak in Internet Explorer may happen because it contains 2 separate garbage collectors (aka GC): one for DOM objects and other for JS properties. Other browsers (FF, Webkit, Chromium, etc.; not sure about the Edge) contains only one GC for both DOM and JS.
So when you create circular reference between DOM object and JS object, IE's GC cannot correctly release the memory and creates a memory leak.
var myGlobalObject;
function SetupLeak()
{
myGlobalObject = document.getElementById("LeakDiv");
document.getElementById("LeakDiv").expandoProperty = myGlobalObject;
//When reference is not required anymore, make sure to release it
myGlobalObject = null;
}
After this code it seems the LeakDiv reference was freed but LeakDiv still reference the myGlobalObject in its expandoProperty which in turn reference the LeakDiv. In other browsers their GC can recognize such situation and release both myGlobalObject and LeakDiv but IE's GCs cannot because they don't know if the referenced object is still in use or not (because it's the other GC's responsibility).
Even less visible is a circular reference created by a closure:
function SetupLeak()
{
// The leak happens all at once
AttachEvents( document.getElementById("LeakedDiv"));
}
function AttachEvents(element)
{
//attach event to the element
element.attachEvent("onclick", function {
element.style.display = 'none';
});
}
In this case the LeakedDiv's onclick property references the handler function whose closure element property reference the LeakedDiv.
To fix these situations you need to properly remove all references between DOM objects and JS variables:
function FreeLeak()
{
myGlobalObject = null;
document.getElementById("LeakDiv").expandoProperty = null;
}
And you may want to reduce (or remove completely) closures created on DOM elements:
function SetupLeak()
{
// There is no leak anymore
AttachEvents( "LeakedDiv" );
}
function AttachEvents(element)
{
//attach event to the element
document.getElementById(element).attachEvent("onclick", function {
document.getElementById(element).style.display = 'none';
});
}
In both cases using try-catch is not the option because the Out of memory may happen on random places in code and even if you find one line of code where it's happened the next time it may be elsewhere. The Process Explorer is the best chance to find the situations when the memory increase and and trying to guess what may be causing it.
For example: if the memory increase every time you open and close the menu (if you have one) then you should look how it's being opened and closed and look for the situations described above.
You could check your localStorage before and after any components called.
Something like:
function getLocalStorage() {
return JSON.stringify(localStorage).length;
}
function addScript(src, log) {
if(log){
console.log("Adding " + src + ", local storage size: " + getLocalStorage());
}
var s = document.createElement( 'script' );
s.setAttribute( 'src', src );
document.body.appendChild( s );
}
function callFunction(func, log){
if(log){
console.log("Calling " + func.name + ", local storage size: " + getLocalStorage());
}
func();
}
try {
addScript(src1, true);
addScript(src2, true);
callFunction(func1, true);
callFunction(func2, true);
}
catch(err) {
console.log(err.message);
}
I hope it helps you. Bye.
I'm working on a Google Scripts add on for Google Sheets, but I'm trying to get the script working before I actually set it up on the sheet. The code below works fine if I set a breakpoint somewhere in the extractNumbers function. If I just execute the code without breakpoints, I get an error:
TypeError: Cannot call method "replace" of undefined. (line 36, file "")
Here's the code:
var myVar = phoneCheck("a1","a2","o1","o2");
Logger.log(myVar);
function phoneCheck(newCell,newHome,oldCell,oldHome) {
Logger.clear();
var newCell = extractNumbers(newCell);
var oldCell = extractNumbers(oldCell);
var newHome = extractNumbers(newHome);
var oldHome = extractNumbers(oldHome);
if (newCell === oldCell) {
return newCell;
exit;
} else if (newCell === oldHome && newHome === oldCell) {
return oldCell;
exit;
}
if (newCell === '' && oldCell !== '' ) {
return oldCell;
exit;
}
if (newCell !== oldCell && newCell !== oldHome) {
return newCell;
exit;
}
return "No value found";
exit;
}
function extractNumbers(input) {
Logger.log(input);
var str = input;
return str.replace( /\D+/g, '');
}
Now I realize my if/then logic is more than a bit inelegant, but for my purposes, quick and dirty is fine. I just need it to run.
ALSO, I have read of other novice JavaScript programmers having similar issues related to the sequence of code execution. If someone would like to link to a concise source aimed at a non-advanced audience, that would be great too. Thanks!
EDIT: I put my code into a new fiddle and it works fine, but it continues to fail in Google Scripts editor unless running in debug mode with a breakpoint. The problem seems to be that the function parameters aren't available to the function unless there is a breakpoint. Anyone have access to Google Scripts that can try my updated code from https://jsfiddle.net/hrzqg64L/ ?
None of the suggestions got to the root of your problem - and neither did your answer, although you've avoided the problem by putting an enclosure around everything.
There's no AJAX, no asynchronous behavior - it's simpler than that. "Shadowing of parameters" is likewise a red herring. Bad coding practice, yes - but not a factor here.
If someone would like to link to a concise source aimed at a non-advanced audience, that would be great too.
Sorry - no such thing. I can explain what's going on, but can't guarantee it will be accessible to novices.
The exception
Let's just clarify what causes the exception, or thrown error, that you've observed.
As written, extractNumbers() will throw an exception if it has a null parameter (or any non-string parameter) passed to it. If you choose to extractNumbers() then hit "run", you'll get:
TypeError: Cannot call method "replace" of undefined. (line 36, file "")
That is telling you that on line 36, which is return str.replace( /\D+/g, '');, the variable str contains an object that is undefined (...and has no replace() method).
For bullet-proof code, you would check your parameter(s) to ensure they are valid, and handle them appropriately. Sometimes that would be with a valid default, and other times you might return an error or throw an exception that is more explicit about the parameter problems.
Running code in Google's debugger
The only way to run code in Google's Debugger is to select a function, then choose "run" or "debug". Assuming you posted all your code, you had just two functions to choose from:
phoneCheck()
extractNumbers()
Whenever Google Apps Script runs any part of a script, the entire script is loaded and scanned to find all symbols & check syntax. The scope of all symbols is noted as well, and so are any dependencies between functions and global symbols (symbols outside of any closure, or block of code).
That takes some time. To speed things up when asked to execute a specific function, the global symbols are only evaluated if they are a dependency for the requested function or the functions it may call. There is another condition that will trigger evaluation of global symbols, and that is if there is a possibility that the debugger may need to stop and display values.
When this happens, any code that is outside a closure (outside a function, for example) will be executed. This is what you observed when you set breakpoints.
Why did it work when breakpoints were set?
As explained, just having a breakpoint set triggers evaluation of global symbols.
You start this script with a few lines of code that are not in any closure:
var myVar = phoneCheck("a1","a2","o1","o2");
Logger.log(myVar);
It is that code which makes the only proper invocation of phoneCheck() with parameters. Because myVar is evaluated, phoneCheck() gets called with parameters, and in turn calls extractNumbers() with a defined parameter.
Unfortunately, because of the way the debugger works, you cannot choose to run that code yourself. You need to rely on these side-effect behaviors.
How to fix this?
Simple. Don't rely on global code to invoke functions under test. Instead, write an explicit test function, and call that.
function test_phoneCheck() {
var myVar = phoneCheck("a1","a2","o1","o2");
Logger.log(myVar);
}
Finally found the issue, but I don't fully understand it.
This question got me thinking about scope and how it might be different in the Google Script environment. I figured a simple workaround would be to enclose the entire script in its own void function, and it worked! Also, I simplified the script quite a bit with an array:
function init () {
var numberArray = ["a3", "a2", "o3", "o10"];
var myVar = phoneCheck(numberArray);
Logger.log(myVar);
function phoneCheck(myArray) {
var phoneString = '';
Logger.clear();
var arrayLength = myArray.length;
for (i = 0; i < arrayLength; i++) {
phoneString += myArray[i].replace(/\D+/g, '');
}
return phoneString;
}
}
Also, I realize the functionality of this script is different than the original, but I was really just trying to solve this problem. Now that I have, I can finish the script properly.
Thanks for all the suggestions, everyone! I learned a lot of good things, even though they turned out not to be the answer.
When i write javascript code , i usually insert a debug symbols to help me out .
Let me illustrate what i mean by example :
var debug = true;
/* Some event handler */
onValueChange = function(e, ui){
var new_value = dom.volume.slider("value");
conf.value = new_value;
if (debug) {
console.log("Value changed to : " + new_value);
}
}
when i finish, i don't want all this debug related code to be part of my release/minified codebase .
what is the convention for this sort of thing ? are there any (non IDE based) tools available ?
I am looking for solutions working with exiting codebase and for starting a new project in the future .
Or what other debugging strategies exist in javascript world ?
To get rid of this logging code you will need to write a parser! I suggest that you go with a different debugging strategy. console.log is no doubt useful but a full fledged debugger like: the one provided by Chrome DevTools, is strongly advised.
You can inject break points at any position in your code with debugger;. Going with a real debugger will make your life as a developer much more easier.
Use FireBug in Fire Fox . it will help you a lot to debug java script code.
I'm writing a decent sized JavaScript animation library, that I would like to include debugging code in. I could easily do a check :
if(myLib.debugger){
console.warn('warning message');
}
However if this runs a couple thousand times a second, it would eventually cause performance issues. Add in a few more checks throughout the code and the effect will be even more noticeable.
What I'm wondering is if it would be possible to check onload if the debugger should be enabled, and if so... turn something like this:
//debugger if(!this.name) console.warn('No name provided');
into:
if(!this.name) console.warn('No name provided');
Leaving the code commented if its not enabled, and uncommenting it if it is, thus removing any possible performance issues. Could this be done somehow with a regular expression on the entire script if loaded in through ajax? I'm trying to avoid the need for 2 versions of the same code, lib.dbug.js and a lib.js.
Cross browser compatibility is not of great importance for this (I'm really only worried about new browsers), I see it as nice to have item. If its possible however, it would be a great thing to have.
Any insight would be greatly appreciated.
The simplest way to do this would be to check if the debugger should be disabled and if so, replace it with a mock object that does nothing at the very start of your script:
if (!myLib.debugger) {
window.console = (function () {
var newConsole = {};
var key;
for (key in window.console) {
if (typeof window.console[key] === 'function') {
newConsole[key] = function () {};
}
}
return newConsole;
}());
}
The overhead of this approach should be negligible.
If this is a JavaScript library... then I'd expect as a 3rd party developer that I could download/use 2 versions. The production version (no debug, AND minimized). If I wanted to debug, I would point to the debug version of the library instead.
e.g.
<script src="foo-lib-min.js"></script>
<!-- swap to this for debugging <script src="foo-lib-full.js"></script>-->
Working on a small engine to run my HTML5 test games, using it as a great way to get deeper into Javascript and have fun at the same time. Succeeding, btw.
However, I just found this cool little script called PreloadJS ( http://www.createjs.com/#!/PreloadJS ). Also using John Resig's classical inheritence class JS. Very cool, enjoying it. I am attempting to use PreloadJS to pull in all of my engine files...but I seem to be having an issue. Here's the code I'm using (keeping it simple on purpose):
var ScriptLoader = Class.extend({ // Want to add functionality to this to allow PHP or inline loading...perhaps later
init: function() {
this.fileList = [
'Empty.js',
'./engine/Scene.js'
];
this.preload;
},
loadProjectSource: function(directory) {
if (this.preload != null) { this.preload.close(); }
this.preload = new createjs.LoadQueue();
this.preload.addEventListener("fileload", this.fileLoaded);
this.preload.addEventListener("error", this.fileError);
this.preload.setMaxConnections(5);
this.loadAnother();
},
loadAnother: function() {
var item = this.fileList.shift();
if(this.fileList.length != 0) {
this.preload.loadFile(item);
}
},
fileLoaded: function(e) {
debug('Loaded ' + e.item.src);
this.loadAnother();
},
fileError: function(e) {
debug('Error ' + e.item.src);
}
}
From my engine instantiation, I'm calling ScriptLoader.loadProjectSource. It's doing nothing but throwing errors, and the documentation on error handling (and loading JS files in general...) is very sparse on the PreloadJS site. It focuses on preloading images (which, admittedly, looks great). Anyway, so yea, it's throwing errors. And it can't be the files, as I tried loading a completely blank JS file (as you can see). Still throwing an error on the Empty.js file.
Annoyed :) Thanks in advance.
The PreloadJS script uses XHR where available with browser support. In order for this to function correctly with locally-hosted scripts, a local webserver must be running. Upon activating my local webserver and attempting the same operation, full success.