
Logging JavaScript Errors in Backbone Applications | Salt Edge
window.onerror
:
window.onerror = function(message, file, line) {
// send error to server
};
Reference: MDN page
Note however that
For historical reasons, the onerror handler has different arguments:(as stated in the HTML spec) That means that in some cases browsers pass 5 arguments to the
onerror
function instead of 3:
window.onerror = function(message, file, line, column, error) {
// in here error.stack returns the stacktrace
};
As you can see above, the 5th argument passed to the function is an error object, which we can inspect to get the stacktrace via different techniques1. But, as the HTML spec says, we cannot rely on the error object to always be there:
function saveError(message, stack) { /* */ }
window.onerror = function(message, file, line, column, error) {
if (typeof error == "undefined") {
saveError(message, []);
} else {
saveError(message, error.stack);
}
};
While this solution is good enough for modern browsers, it will lack any meaningful context in older ones. That is a bit of a problem since most of the time bizarre JavaScript errors happen in legacy browsers.
However, if we think about it, there’s actually a single, unified way a user can change a Backbone application state – by triggering an event that is handled with a view.
So if we can override Backbone event handler to do a try/catch before dispatching an event, we can inspect that error to deduce context. To do that, we must break open Backbone.View.prototype
and replace delegateEvents
function implementation:
_.extend(Backbone.View.prototype, {
// We need to save the original implementation to call it later
originalDelegateEvents: Backbone.View.prototype.delegateEvents,
delegateEvents: function(incomingEvents) {
var key, method,
wrappedEvents = {},
events = incomingEvents || _.result(this, "events");
// We don't have anything to do if the view has no events
if (!events) { return this; }
for (key in events) {
// Ensure that eventHandler is a function before wrapping it.
eventHandler = events[key];
eventHandler = _.isFunction(eventHandler) ? eventHandler : this[eventHandler];
// Take the event handling function,
// wrap it in a try/catch block,
// and replace it in the events declaration passed to Backbone
wrappedEvents[key] = this.wrapEventHandler(key, eventHandler);
}
return this.originalDelegateEvents(wrappedEvents);
},
wrapEventHandler: function(event, handler) {
return function() {
try {
// Call the original handler with all the arguments, and catch any errors
// that occur
return handler && handler.apply(this, arguments);
} catch (error) {
return saveError(error.message, error.stack);
}
};
}
});
Given that we are catching event errors, we have also some additional context for debugging: the event that was dispatched. So we can modify our saveError
function to accept additional context:
function saveError(message, stack, context) {
if (_.isUndefined(context)) { context = {} }
// save message, stack and context here
}
In addition, we need to change our wrapEventHandler
function to pass that context to the saveError
function:
_.extend(Backbone.View.prototype, {
//...
wrapEventHandler: function(event, handler) {
return function() {
try {
return handler && handler.apply(this, arguments);
} catch (error) {
// Notice the 3rd argument here
return saveError(error.message, error.stack, {event: event});
}
};
}
}
If we take the code above and create a very small Backbone view with an error in the event handler, it will look like this:
<iframe><width=”100%” height=”300″ src=”https://jsfiddle.net/alisnic/zx3340xp/3/embedded/result,js,html/” allowfullscreen=”allowfullscreen” frameborder=”0″></iframe>
As you saw above, we successfully intercepted an error that occurred in the event handler. Using the same technique, we can override,Backbone.Router.prototype.route
and catch errors that occur in route handlers.
Note that our approach does not exclude,window.onerror
but rather complements it. There are a lot of cases when errors are triggered outside view events.
Pros:
- in contrast with
window.onerror
, our approach will always have access to the native error object. We can get the stacktrace from it via different techniques. - by catching the errors in the different contexts, we can save additional information from them, which helps to debug the problem
- we break open
Backbone
guts and we must pay extreme attention when upgrading to not break anything2
- You can use
error.stack
or arguments.callee when any of them is available. There are libraries for that, like stacktrace.js ↩ - You can avoid prototype patching by making a base view that extends the Backbone view and overrides the desired behavior ↩
Salt Edge report
Discover what is the current state of open banking payments in Europe in 2021