Logging JavaScript Errors in Backbone Applications | Salt Edge

When developing rich JavaScript applications such as fentury.com, a lot of business logic runs on the client-side. In order to provide an excellent user experience, the developers must have insight into any errors that occur in application runtime. In other words, the team must have a way to be notified when an error occurs during the application execution. The canonical way of capturing errors in an HTML+JS app is 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 delegateEventsfunction 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/&#8221; 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
Cons:
  • we break open Backbone guts and we must pay extreme attention when upgrading to not break anything2

  1. You can use error.stack or arguments.callee when any of them is available. There are libraries for that, like stacktrace.js 
  2. 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

Download now

Related articles

4 min read Nov 2015

Salt Edge contributing to Open-Source with Salt-Parser | Salt Edge

There are lots of formats to store or read financial data; Salt Edge is supporting 3 most widely used: OFX, QIF and SWIFT. Why are these formats so important? Open Financial Exchange (OFX). A data stream format for exchanging financial information that evolved from Microsoft’s Open Financial Connectivity (OFC) and…

3 min read Nov 2022

Salt Edge names Stephen Winyard as Chief Sales Officer

Salt Edge, a global leader in offering open banking solutions, has reinforced its upper management with its latest hire, appointing Stephen Winyard as Chief Sales Officer, who brings to Salt Edge a wealth of experience and proven multi-million dollar growth in numerous technology companies throughout his career. Having Stephen on…

6 min read Mar 2019

Is your bank ready for the first major PSD2 deadline?

One more day remains until the next PSD2 milestone. Tomorrow, the EU banks should make available a sandbox to third party providers for connection and functional testing of their applications and how they interoperate with the bank interface. This can be regarded as a rehearsal for the premier of a…

7 min read Apr 2021

Why open banking APIs are so different

Open banking comes with a lot of expectations and promises, such as democratisation of Access to Account (X2A), increased competition between banks and fintechs, and provision of better control to end-customers over their financial data and payments. To facilitate the adoption of open banking, several API standards incentives were created…