Error handling in Javascript

I was helping someone today with their javascript and they asked me about how and when I manage errors in the classes and functions that I write. I wrote him back an email which I think actually could be useful to others, so here it is.

There are three types of error handling that I use:

  • Graceful: if possible, just ignore the error and continue with some default state or without a meaningful value
  • Debug: throw a warning to the dbug.log method but continue otherwise
  • Break: Either explicitly throw an error or (more often) just let the error that is thrown at runtime be thrown

Graceful

Think of this in the same way as the options pattern. You have some default behavior that you’ll go with if you don’t have data you need. A great example here is StickyWin:plain textJavaScript:
this.id = this.options.id || ‘StickyWin_’+new Date().getTime();

drag to resize

Basically I let the user specify a date, but if they don’t give me one, I just make one up. This ID isn’t used by my code anywhere but instead is something I added so that I can select a StickyWin div in the console if I ever want to for debugging or something. I got tired of having to dig through all the StickyWins on the page with $$(“.StickyWinInstance”) so I started assigning them IDs so I could grab the divs by looking in firebug at the DOM and then $(id) to get the div and muck with it. I couldn’t declare this default ID value in the options because it must be computed at run time (to get a unique date for each instance).

I often include try/catch statements in my code where I just want to write the code and let things fail if they’re going to. I will throw a dbug error in the catch statement. Check out popupdetails.js:plain textJavaScript:
getContent: function(){
try {
new Ajax((this.options.ajaxLink || this.options.observer.href), $merge(this.options.ajaxOptions, {
                onComplete: this.show.bind(this)
            })
        ).request();
    } catch(e) {
        dbug.log(‘ajax error on PopupDetail: %s’, e);
    }
}

drag to resize

This error may or may not be fatal. Actually, I should have an event here (this.fireEvent(‘ajaxError’, e)) or something.

Debug

There’s a difference between arguments required and options. Consider the Fx library. You can’t have an effect without an element, so instead of the element being an optional value, it’s a required argument for instantiation (while options are just that – optional). I’ve only recently started writing my code this way but it makes sense. If you want to create an enhanced input, the input shouldn’t be in the options – it should be an argument.

But sometimes you DO have things that are both required AND optional. For example, a slideshow. If you have methods to add slides after you create the instance, then you don’t need to pass any as arguments because you can add them later. If you can do mySlideShow.add(slide) then it makes sense that you could create an instance without any slides to start with. But this behavior is not really obvious to the user and so you want to give them a hint if they create an instance with no slides. Maybe they do new SlideShow({slides: $$(‘img.slide’)}) and their selector returns an empty array. They might spend a while trying to figure out that they didn’t pass any to begin with because their selector is wrong.

Your code can still iterate over the passed in slides (an empty array won’t throw errors if you try and iterate over it; it’ll just iterate zero times). So it degrades gracefully as you design it and waits for the user to add items manually. But in such cases I often use dbug.log to inform the user that they may have not used the class correctly.

Oddly enough, I don’t actually do this with my slideshow, but I do use this method in popupdetails.js. Popupdetails associates data with items in the page so that if the user hovers over an icon corresponding data is displayed. But what happens if the data supplied do not match in number to the items observed? Well, it’s conceivable that there are only 9 data for the 10 thumbnails, an in that case I just ignore the tenth thumbnail, but I DO warn the user (the coder) that this is going on:plain textJavaScript:
var ln = this.options.ajaxLinks.length;
if(ln <= 0) ln = this.options.details.length;
if (this.options.observers.length != ln)
    dbug.log(“warning: observers and details are out of sync.”);

drag to resize

Break

You can actually throw errors in Javascript. Here’s Date.js:plain textJavaScript:
ret = Date.$months[month – 1] || false;
if (!ret) thrownew Error(‘Invalid month index value must be between 1 and 12:’ + index);

drag to resize

This will behave like any other run time error and basically should never happen in production. It is fatal error and there is no way to gracefully handle it. I don’t use throw very much. It’s much more likely that if you initialize one of my classes without a required argument the class will just break on its own. Let’s consider Fx.Style. If you passed an invalid element to it, it’s going to break fast:plain textJavaScript:
initialize: function(el, property, options){
this.element = $(el);
this.property = property;
this.parent(options);
},

increase: function(){
this.element.setStyle(this.property, this.css.getValue(this.now, this.options.unit, this.property));
}

drag to resize

The increase method attempts to reference the method setStyle. If the element you passed as an argument was not an element (or an id that was not found in the DOM), then it is the boolean false, which has no such method as setStyle. So it’ll just flat out break. There’s no need to handle this error in a graceful fashion as it is fatal.

If you write your code well, you’ll quietly catch error conditions that can be managed with some assumptions, inform the user when those assumptions might be confusing, and throw errors when there are no assumptions to make and the error is fatal.

Leave a Reply

Your email address will not be published.