View Poll Results: What do you think abotu this approach

Voters
1. You may not vote on this poll
  • Awesome, long live Zakas (and Roland :-P)

    0 0%
  • not really sure what to think, what's about performance

    0 0%
  • I don't like this approach

    1 100.00%
  1. #1
    Sencha User Dumas's Avatar
    Join Date
    Dec 2008
    Location
    Vienna, Austria
    Posts
    583
    Vote Rating
    9
    Dumas will become famous soon enough

      0  

    Default Handling unexpected errors in apps

    Handling unexpected errors in apps


    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?


    PHP 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(namemethod){
                    return function(){
                        try {
                            return 
    method.apply(thisarguments);
                        } catch (
    ex) {
                            if(
    window.console && typeof window.console.error === 'function') {
                                
    window.console.error('catched error in function ' name "(): " ex.message); //FIXME
                            
    }
                        }
                    };

                }(
    namemethod));
            }
          }
        }


        
    // 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(superclassoverrides){
        
    // 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 
    NewComponentNewIntermediateComponentnewObject;
    /* 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

  2. #2
    Sencha - Ext JS Dev Team Animal's Avatar
    Join Date
    Mar 2007
    Location
    Notts/Redwood City
    Posts
    30,508
    Vote Rating
    58
    Animal has a spectacular aura about Animal has a spectacular aura about Animal has a spectacular aura about

      0  

    Default


    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

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

  3. #3
    Sencha User Dumas's Avatar
    Join Date
    Dec 2008
    Location
    Vienna, Austria
    Posts
    583
    Vote Rating
    9
    Dumas will become famous soon enough

      0  

    Default


    Quote Originally Posted by Animal View Post
    Horrible. Awful!
    Interessting. I heard things from "great" till "awefull", seems like this is very controversial.

    Quote Originally Posted by Animal View Post
    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"^^


    Quote Originally Posted by Animal View Post
    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

  4. #4
    Sencha - Ext JS Dev Team Animal's Avatar
    Join Date
    Mar 2007
    Location
    Notts/Redwood City
    Posts
    30,508
    Vote Rating
    58
    Animal has a spectacular aura about Animal has a spectacular aura about Animal has a spectacular aura about

      0  

    Default


    Classes created using your extend enhancement:

    Code:
    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.

Similar Threads

  1. AJAX not properly handling errors???
    By PCSpectra in forum Ext 3.x: Help & Discussion
    Replies: 0
    Last Post: 1 Feb 2010, 7:49 AM
  2. Handling Server errors
    By thezozo in forum Ext 2.x: Help & Discussion
    Replies: 1
    Last Post: 16 Sep 2008, 2:37 PM
  3. Unexpected errors with Record et al
    By posta07 in forum Ext GWT: Bugs (1.x)
    Replies: 0
    Last Post: 24 Jul 2008, 3:19 PM
  4. Ideas for handling Javascript errors
    By DrZog in forum Community Discussion
    Replies: 0
    Last Post: 28 Apr 2007, 12:31 PM

Thread Participants: 1