Reputation: 3359
My group is starting a new project. We are thinking about organizing the JavaScript in such a way so that any JavaScript errors don't get lost but rather get caught and sent to the server to be logged. For namespacing I want to keep it simple so I'm using something like this:
var my_namespace = function() {
function myFunction(input) {
if (input < 0) {
throw "input must be positive";
}
return 'result';
}
return {myFunction: myFunction};
} ();
So now I can invoke my_namespace.myFunction(-22) but when the error will be thrown it will get lost. There will be many namespaces each one in it's own .js file (maybe somebody has a better idea about namespace schema).
So, my question is, how to like "surround" namespaces so that errors will not get lost?
Actually @Relic gave a good idea. I'm going to write below the code that will create the namespace "my_namespace" and surround the initialization by jQuery with try-catch:
var my_namespace = function() {
function init() {
throw "an exception during initialization";
}
return {init: init};
} ();
$(document).ready(function() {
try {
my_namespace.init();
} catch (e) {
// handle error
}
});
I'm going to experiment with what happens after it does initialization, that is, with the event handling.
Yep, just as I thought, event handling exceptions will not be caught. I'll research some more and return.
Upvotes: 2
Views: 1169
Reputation: 3359
After spending some time looking for the solution I came to the following conclusion. You must use window.onerror, there is no other way.
Upvotes: 0
Reputation: 1074495
Two options for you:
You can wrap all of your code with try/catch
blocks. This isn't as tedious as it sounds. There are two aspects of this: Wrapping your main code, and wrapping code that runs in response to events (user events, timer events, etc.). You can either do that manually, or you can give yourself a framework for doing it.
This doesn't have to be a pain at all. For instance, for the first part, just wrap a try/catch
around your main code:
(function() { // (If you don't use scoping functions, just ignore this and the last line
try {
// Your code here
}
catch (e) {
reportException(e);
}
function reportException(exception) {
try {
// Do whatever you want to do to report the exception here.
}
catch (e) {
// Let the browser report it
throw 'Error handling exception: ' + exception;
}
}
})();
For the second part (catching exceptions in event handlers and code fired with setTimeout
and similar), you can either always manually use try/catch
blocks in all of your code (which is frequently what you want to do anyway), and possibly use a central function that wraps your event handlers to make sure uncaught exceptions are caught and handled, like this:
function makeHandler(handler) {
eventHandler.original = handler;
return eventHandler;
function eventHandler(event) {
try {
// Trigger the handler
return handler.call(this, event);
}
catch (e) {
// Handle event handler exception
reportException(e);
}
}
}
(There are more features you might add to that, but those are the basics.)
For public methods, you can use something quite similar to makeHandler
:
function makePublic(method) {
publicMethod.original = method;
return publicMethod;
function publicMethod() {
try {
// Trigger the actual member
return method.apply(this, arguments);
}
catch (e) {
// Handle reporting the exception
reportException(e);
// Then probably re-throw it so the calling code
// sees it
throw e;
}
}
}
Bringing that all together, this code:
var Namespace = (function() {
var NS = {};
// Some setup
doSomething();
doSomethingElse();
if (/* Some condition */) {
doYetAnotherThing();
}
// Export public methods
NS.foo = foo;
NS.bar = bar;
function doSomething() {
var element = document.getElementById("foo");
// Note, next line could throw if element doesn't exist
element.addEventListener("click", function(event) {
// Handling click
var other = element.getElementsByTagName('input')[0];
element.innerHTML = other.value; // Could throw if `other` not there
}, false);
}
// ...other functions, including `foo` and `bar`...
// Return the namespace object
return NS;
})();
Turns into:
var Namespace = (function() {
var NS = {};
try {
// Some setup
doSomething();
doSomethingElse();
if (/* Some condition */) {
doYetAnotherThing();
}
// Export public methods
NS.foo = makePublic(foo);
NS.bar = makePublic(bar);
}
catch (e) {
reportException(e);
}
function doSomething() {
var element = document.getElementById("foo");
// Note, next line could throw if element doesn't exist
element.addEventListener("click", makeHandler(function(event) {
// Handling click
var other = element.getElementsByTagName('input')[0];
element.innerHTML = other.value; // Could throw if `other` not there
}), false);
}
// ...other functions, including `foo` and `bar`...
// ...`reportException`, `makeHandler`, `publicMethod`...
// Return the namespace object
return NS;
})();
So it's not that much impact.
You always want to use more targeted try/catch
as part of your logic, but you can also use these global try/catch
blocks to catch anything unexpected (like those silly bugs that sometimes slip into the catch
block!), etc.
There are several advantages to this approach:
It works on all browsers.
You can throw things other than strings (more structured exceptions), and they'll still be objects when you catch them.
If an error reaches the browser level, you know it's not in your code, or it's in your exception reporting code.
window.onerror
If for whatever reason the above isn't to your taste, a little-known feature of the browser environment is the fact that you can trap any uncaught error by assigning a function to window.onerror
(live example described and linked below):
window.onerror = globalErrorHandler;
function globalErrorHandler(errorMsg, url, lineNumber) {
// Do something with the error here
}
This works in most browsers, but not all, and suffers from the fact that chaining these sorts of error handlers isn't natively supported (you have to do it yourself) and by the time the error reaches your code, it's already been turned into a string (a pain if you're using more structured exception objects).
Details on the MDC page for it, including how to play nice with others; slightly modified example:
function addWindowErrorHandler(handler) {
var previous = window.onerror;
window.onerror = function(errorMsg, url, lineNumber) {
var returnValue = false,
handled = false;
// Call the handler
try {
returnValue = handler(errorMsg, url, lineNumber);
}
catch (e) {
// Eat the error
}
// Hand off to previous
if (!returnValue && previous) {
try {
returnValue = previous(errorMsg, url, lineNumber);
}
catch (e) {
// Just eat it
}
}
// Done
return returnValue;
};
}
Just call that with a reference to your handler function, and have your handler function return true
if the error was yours to handle, false
otherwise.
To know whether the error is yours or not, you might consider putting a marker in the string (sadly, it'll be a string by the time it reaches the onerror
handler, even if you threw some other object type). So you might use a worker function for the whole module that adds a marker, e.g.:
function myException(msg) {
return '*Marker* ' + msg;
}
Then
throw myException('cannot be negative');
and your handler would do
if (String(error).indexOf('*Marker*') >= 0) {
// It's ours
// ...handle it...
// Flag that we handled it
return true;
}
Unfortunately, even though you process the error, I'm not aware of any way to suppress it (the script still stops executing at that point).
You could even have Exception
objects you construct that accept a message and a nested exception if you like. Just be sure to handle toString
on them, because (again) by the time the error gets to the error handler, it's already been turned into a string.
Upvotes: 2
Reputation: 5897
My Official answer:
<input type="button" id="btn" name="dont click me" value="dont click me" />
var _ns = { init: function() {
this.events();
},
events: function(){
$("#btn").on('click mouseover', function(event){
if(event.type != "mouseover"){
_ns.error.alert("Annnd you clicked me anyways");
}else{
_ns.error.console("nice mouseover skillz");
}
});
},
error:{
console:function (Error) {
console.log("Error: "+Error);
},
alert: function(Error){
alert("Error: "+Error);
}
}
};
$(document).ready(function() {
_ns.init();
});
Upvotes: 0
Reputation: 41767
I'd recommend a block of code that can monkey patch any function calls (nb untested):
(function (namespace) {
var addTryCatch = function (delegate) {
try {
delegate.apply(this, arguments);
} catch {
// standard code here
}
};
for (var propName in namespace) {
var prop = namespace[propName];
if (typeof prop === 'function') {
namespace[propName] = addTryCatch(namespace[propName]);
}
}
}(yourNamespace));
Any recursion could be added if necessary.
Upvotes: 1