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

11 min read Mar 2023

Open banking – a bespoke solution for responsible iGaming

iGaming – a sector which covers ​​any form of online wagering, despite being a relatively young industry, has snowballed over almost 20 years of existence. A study conducted by Statista reported that in 2021, the global online gambling and betting industry was valued at US$ 61.5 billion and is expected…

5 min read Jun 2021

If open banking were a colour – it would be green

Did you know that approximately 94% of European citizens believe that protecting the environment is very important, while only little over 80% of them would agree to be part of actions protecting the environment? 80 percent doesn’t seem enough, considering that environmental damage is increasing daily. The good news is…

4 min read Jan 2023

Salt Edge brings Open Banking solutions to the Hashemite Kingdom of Jordan

Salt Edge, a leader in building Open Banking API solutions, has announced its expansion to a new market – the Hashemite Kingdom of Jordan. The company’s goal is to assist Jordanian banks and other local financial institutions in offering innovative solutions, contributing to the Kingdom’s efforts toward a digital future…

4 min read Dec 2022

ebankIT teams up with Salt Edge to bring a full-scale of open banking solutions

ebankIT, a fintech company that enables digital experiences for banks and credit unions, partnered with Salt Edge, a leader in offering open banking solutions, to help financial services providers in Canada, EMEA, and beyond to leverage the full spectrum of open banking features – fast and secure. Technological advancements have…