JavaScript Timer and Stack Trace Debugging

Today I had to work on a webpage with a large amount of JavaScript timers running and I had to find a bug which one of them caused. Unfortunatly the timeline in the Google Chrome developer tools only showed the order of the timer execution together with the timer ID. That IDs are created by each JavaScript setInterval() and setTimeout call. But just the ID doesn’t help much. So I rewrote the prototype of both the setTimeout and setInterval functions:


window._oldTimeOut = window.setTimeout;
window.setTimeout = function(execute, timeout) {
	var id = window._oldTimeOut(execute, timeout);
	console.debug("created timer with id:", id, arguments.callee.caller.toString());
	console.debug('execute',execute.toString());
	return id;
};

window._oldInterval = window.setInterval;
window.setInterval = function(execute, timeout) {
	var id = window._oldInterval(execute, timeout);
	console.debug("created interval with id:", id, arguments.callee.caller.toString());
	console.debug('execute',execute.toString());
	return id;
};

This gave me at least a hint where the sourcecode lines of the timer-executed code can be found. Also very helpful is a stack trace (aka call stack) at the current code position to find out which other function was calling the current one. I found a pretty old but still working code snippet on http://helephant.com/2007/05/diy-javascript-stack-trace/ and added it to my timer debugging:


Function.prototype.trace = function() {
	var trace = [];
	var current = this;
	while(current) {
		trace.push(current.signature());
		current = current.caller;
	}
	return trace;
};

Function.prototype.signature = function() {
	var signature = {
		name: this.getName(),
		params: [],
		toString: function() {
			var params = this.params.length > 0 ? "'" + this.params.join("', '") + "'" : "";
			return this.name + "(" + params + ")";
		}
	};
	if(this.arguments) {
		for(var x=0; x<this.arguments.length; x++)
			signature.params.push(this.arguments[x]);
	}
	return signature;
};

Function.prototype.getName = function() {
	if(this.name)
		return this.name;
	var definition = this.toString().split("n")[0];
	var exp = /^function ([^s(]+).+/;
		if(exp.test(definition))
			return definition.split("n")[0].replace(exp, "$1") || "anonymous";
		return "anonymous";
	};

	window._oldTimeOut = window.setTimeout;
	window.setTimeout = function(execute, timeout) {
		var id = window._oldTimeOut(execute, timeout);
		console.debug("created timer with id:", id);
		console.debug('execute',execute.toString());
		console.debug("stacktrace", arguments.callee.trace());
		return id;
	};

	window._oldInterval = window.setInterval;
	window.setInterval = function(execute, timeout) {
		var id = window._oldInterval(execute, timeout);
		console.debug("created interval with id:", id);
		console.debug('execute',execute.toString());
		console.debug("stacktrace", arguments.callee.trace());
		return id;
	};
};

Be sure to add that script tag at the very top of your JavaScript execution (as the first element of your <head> tag for example).

Advertisements