PDA

View Full Version : Handling unexpected errors in apps



Dumas
1 Sep 2010, 3:28 AM
Hello Guys!

I was thinking about error handling in production mode. I sometimes make errors in code I deploy, so I was thinking about how to handle them on the client side and that I would like to send those erorrs to a log file as well.

I heard a talk from Nicholas C. Zakas where he described a function which surounds all methods of an object with a try-catch for production. So users never see js exception thrown from the browser and I can handle them gracefully. I liked that and had the idea why not do this with all methods.

In ExtJS there are two common ways to add code, writting an extention (therfore I modify Ext.extend) or adding custom function to the config object while creating a new instance (therfore I modiyfied the component constructor). There also can be plugins, they are handled when adding to an component.

Of course this will not cover js code that is not inside a class or instance directly, but there you still can use the productionize(obj) by hand.

What do you think about this idea and/or code?




/**
* adds try-catch to all functions of an object
* (probably still create errors with wrapper functions for private scope)
*
* code is based Nicholas C. Zakas productionize (MIT Licensed)
* more info at http://www.nczonline.net/blog/2009/04/28/javascript-error-handling-anti-pattern/
* @param {Object} object all methods of this object are getting a try-catch
* @param {Boolean} extComponent if true, all available nested methods up till panels get productionized too (requires ExtJS, default: false)
* @param {Boolean} extButton if true, all available nested methods get productionized too (requires ExtJS, default: false)
*/
function productionize(object,extComponent,extButton){

var name,
method;

// add try-catch to all methods
for (name in object){
if(object.hasOwnProperty(name)) {
method = object[name];
if (typeof method === 'function'){
object[name] = (function(name, method){
return function(){
try {
return method.apply(this, arguments);
} catch (ex) {
if(window.console && typeof window.console.error === 'function') {
window.console.error('catched error in function ' + name + "(): " + ex.message); //FIXME
}
}
};

}(name, method));
}
}
}


// productionize all nested methods for ext components
if(window.Ext) {
if(extComponent || extButton) {
if(typeof object.listeners === 'object') {
productionize(object.listeners);

// listeners can be objects as well, so handle those also
Ext.each(object.listeners, function(el,i) {
if(Ext.isObject(el)) {
productionize(el);
}
});
}
}

var productionizeArrayOrObject = function(obj,underneathIsExtComponent) {
if(Ext.isArray(obj)) {
Ext.each(obj, function(el,i) {
productionize(el,underneathIsExtComponent);
});
} else {
productionize(obj,underneathIsExtComponent);
}
};

if(extComponent) {
if(typeof object.bbar === 'object') productionizeArrayOrObject(object.bbar);
if(typeof object.fbar === 'object') productionizeArrayOrObject(object.fbar);
if(typeof object.tbar === 'object') productionizeArrayOrObject(object.tbar);
if(typeof object.items === 'object') productionizeArrayOrObject(object.bbar,true);
if(typeof object.buttons === 'object') productionize(object.buttons,false,true);
if(typeof object.defaults === 'object') productionize(object.defaults);
if(typeof object.plugins === 'object') productionize(object.plugins);
if(typeof object.tools === 'object') productionize(object.tools,false,true);
}
}
} //eo productionize

// productionize all extentions (ExtJS is already laoded with all classes, so only my code will be productionized)
var extendFunction = Ext.extend;
Ext.extend = function(superclass, overrides){
// productionize all override methods
productionize(overrides);

// now call standard extend method
return extendFunction.apply(extendFunction,arguments); // extend is created by a wrapper function, so scope is as well extendFunction
};

// also productionize all funtions added at instanziation time
// therefore do this in the component call (the Observable constructor don't have direct access to the config obj)
var ComponentConstructor = Ext.Component;
Ext.Component = function(config) {
// productionize all config methods
productionize(config);

// now call standard constructor
return ComponentConstructor.apply(this,arguments); // use this, cause the new statement create a new scope
};
// fix the superclass chain
Ext.Component.superclass = ComponentConstructor.superclass;
// fix the prototype chain
Ext.Component.prototype = new ComponentConstructor();








var NewComponent, NewIntermediateComponent, newObject;
/* test cases for extend */
// test simple case
NewComponent = Ext.extend(Ext.Component, {
throwException: function() {
this.createUndefinedException();
}
});
// test if the new extend catch exceptions
newObject = new NewComponent();
try {
newObject.throwException();
console.info('productionizing extend works');
} catch (ex) {
console.info('productionizing extend doesn\'t work!');
console.info(ex);
}
// test over multiple extends
NewIntermediateComponent = Ext.extend(Ext.Panel, {
throwFirstException: function() {
this.createUndefinedException();
}
});
NewComponent = Ext.extend(NewIntermediateComponent, {
throwSecondException: function() {
this.createUndefinedException();
}
});
// test if the new extend catch exceptions
newObject = new NewComponent();
try {
newObject.throwFirstException();
console.info('productionizing complex extends works');
} catch (ex) {
console.info('productionizing complex extends doesn\'t work!');
console.info(ex);
}
// test if the new extend catch exceptions
newObject = new NewComponent();
try {
newObject.throwSecondException();
console.info('productionizing complex extends works');
} catch (ex) {
console.info('productionizing complex extends doesn\'t work!');
console.info(ex);
}


/* test cases for constructor function */
newObject = new Ext.Component({
throwException: function() {
this.createUndefinedException();
}
});
try {
newObject.throwException();
console.info('productionizing constructor works');
} catch (ex) {
console.info('productionizing constructor doesn\'t work!');
console.info(ex);
}
/* end of test cases */

//eof
thx
Roland

Animal
1 Sep 2010, 4:32 AM
Horrible. Awful!

Wasteful.

And won't work.

You can't poke in new functions as instance properties and expect things to work.

In a lot of cases, function references have already been taken, and so your replacements won't get called.

If you want a central application error handler use



if (window.addEventListener) {
Ext.fly(window).on({
error: myGlobalErrorHandlerFn
});
} else {
window.onerror = myGlobalErrorHandlerFn;
}

Dumas
2 Sep 2010, 5:27 AM
Horrible. Awful!
Interessting. I heard things from "great" till "awefull", seems like this is very controversial.


You can't poke in new functions as instance properties and expect things to work.
I tried a few things and it worked fine, I do the changes to the config object BEFORE it is used at all, so that worked fine so far...
It's still that one part of me says "oh, great, best production debugging and autoamtic error handling ever" and the other one "uhh, that can create unexpected problems with wrapper functions and will slow down everything, uncool"^^



If you want a central application error handler use [.... onerror event... ]
That's what I'm doing now, but there are cases where this doesn't help to supress the errors. E.g. in IE sometimes the browser bypass the error event (http://msdn.microsoft.com/en-us/library/ms976144.aspx), there are also some cases in ff.

Best regards
Roland

Animal
2 Sep 2010, 5:37 AM
Classes created using your extend enhancement:



var extendFunction = Ext.extend;
Ext.extend = function(superclass, overrides){
// productionize all override methods
productionize(overrides);

// now call standard extend method
return extendFunction.apply(extendFunction,arguments); // extend is created by a wrapper function, so scope is as well extendFunction
};


should work OK, because the prototype methods get replaced before any instantiation, so your solution is miles better than that blogger guy's solution.