Recommended practice for application exception handling in AngularJS

后端 未结 3 772
遥遥无期
遥遥无期 2021-01-31 03:42

I am currently exploring possible methods to handle application-wide exceptions in AngularJS.

One of the things we really wanted to avoid was wrapping multiple parts of

相关标签:
3条回答
  • 2021-01-31 03:46

    You can override the $exceptionHandler in order to pass the exceptions to your own central service for exceptions, but the $exceptionHandler seems to only receive the exceptions thrown from your controllers, directives, etc... but not for the exceptions originated from ajax calls. For those exceptions you can implement an interceptor like the one described in this page:

    EDITED: Link is dead permanently.
    Archive.org link

    0 讨论(0)
  • 2021-01-31 03:52

    I was thinking about the same recently, and it occurred to me that when it comes to a good error handling in javascript, it is irrelevant which framework you are using, Angular on something else. I wrote one such error handler recently for an AngularJS project, but I did it in a way it can be used in any framework.

    Here's the complete code. You can either use it directly, or modify to your needs...

        /*
    Factory errorFact is to simplify error handling and reporting in other objects.
    It supports detailed error output as a text string and into the browser's console.
    
    Usage example:
    
    A function that supports return of an error object would have the following declaration
    as its very first line:
    
    var e = errorFact.create("objectName.funcName", arguments);
    - in this declaration we specify the full object + method name as the first string parameter,
    - and as the second parameter we pass javascript's reserved variable called arguments, which
      provides reference to all of the function's parameters for logging.
    
    When an error occurs, the function would return:
    
    return e.error("Error description text");
     - this line will create and return a complete error context.
    
    When a function that supports return of an error object makes a call into another
    function that also supports the error context, then it can return the nested error
    result by passing the embedded error to the current error object instead of the error
     text.
    
     Example:
    
     var e = errorFact.create("objectName.funcName", arguments);
     var data = callAnotherFunc(...); // calling a function that support an error object;
     if(data.isError){ // If an error was triggered;
        return e.error(data); // return that error from the current context;
     }
    
     The top-level code that calls an error-returning function would do verification
     and if an error occurred, log all its details into console (typically).
    
     Example:
    
     var data = getData(...);
     if(data.isError){
        data.log(); // Output all the error details into the browser's console;
     }
     */
    
    "use strict";
    
    app.factory("errorFact", function(){
        return {
            // creates a new error context;
            create: function(method, args){
                var result = {
                    // initiates and returns the error context;
                    error: function(msg){
                        this.info.isError = true;
                        if(msg.isError){
                            this.info.details.caller = msg;
                        }else{
                            this.info.details.msg = msg;
                        }
                        return this.info;
                    },
                    info:
                    {
                        isError: false,
                        details: {},
                        log: function(){
                            if(this.isError){
                                console.error(this.format());
                            }
                        },
                        // formats complete error details into a text string;
                        format: function(){
                            if(this.details.caller){
                                var txt = this.details.caller.format();
                                txt += "\nCALLER: " + this.details.method + "(" + this.formatArguments() + ")";
                                return txt;
                            }
                            if(this.details.method){
                                return "Error calling " + this.details.method + "(" + this.formatArguments() + "): " + this.details.msg;
                            }else{
                                return this.details.msg;
                            }
                            return "";
                        },
                        // formats function argument details into a text string;
                        formatArguments: function(){
                            if(!this.details.args){
                                return "";
                            }
                            var params = "";
                            for(var i = 0;i < this.details.args.length;i ++){
                                if(params.length > 0){
                                    params += ",";
                                }
                                var p = this.details.args[i];
                                if(p === undefined){
                                    params += "undefined";
                                }else{
                                    if(p === null){
                                        params += "null";
                                    }else{
                                        if(typeof(p) == "object"){
                                            params += "Object";
                                        }else{
                                            params += p;
                                        }
                                    }
                                }
                            }
                            return params;
                        }
                    }
                };
                if(method){
                    result.info.details.method = method;
                }
                if(args){
                    result.info.details.args = args;
                }
                return result;
            }
        }
    });
    

    Below is a factory that shows how it is used:

        "use strict";
    
    app.factory('moduleFact', ['errorFact', function(errorFact){
        return {
            // Locates existing module and expands its key Id references
            // into corresponding object references:
            // - If 'hintGroupId' is present, property 'hints' is added from
            //   the corresponding hint group.
            // - If 'repModules' is present, properties 'question' and 'refs'
            //   are added.
            // On success, return the expanded module object.
            // On failure, returns an error object.
            //
            // NOTE: Currently supports only the first value in repModules.
            expandModule: function(moduleData, moduleId){
                var e = errorFact.create("moduleFact.expandModule", arguments);
                if(!moduleData || !moduleData.modules || !moduleId){
                    return e.error("Invalid parameters passed");
                }
                var mod = this.findModule(moduleData, moduleId);
                if(mod.isError){
                    return e.error(mod);
                }
                var src = mod;
                if(mod.repModules){
                    var repId = mod.repModules[0];
                    if(!repId){
                        return e.error("Invalid repModules encountered");
                    }
    
                    ///////////////////////////////////////
                    // temporary check to throw a warning:
                    if(mod.repModules.length > 1){
                        console.warn("Multiple values in property repModules: " + JSON.stringify(mod.repModules) +
                            ", which is not supported yet (only the first value is used)");
                    }
                    ///////////////////////////////////////
    
                    src = this.findModule(moduleData, repId);
                    if(src.isError){
                        return e.error(src);
                    }
                }
                if(src.question){
                    mod.question = src.question;
                }else{
                    return e.error("Question not specified");
                }
                if(src.refs){
                    mod.refs = src.refs;
                }
                if(src.hintGroupId){
                    var hg = this.findHintGroup(moduleData, src.hintGroupId);
                    if(hg.isError){
                        return e.error(hg);
                    }
                    mod.hints = hg.hints;
                }
                return mod; // needed extra: expand attribute repModules
            },
            // Expands all the modules and returns the data;
            expandAllModules: function(moduleData){
                var e = errorFact.create("moduleFact.expandAllModules", arguments);
                if(!moduleData || !moduleData.modules){
                    return e.error("Invalid parameters passed");
                }
                for(var i = 0;i < moduleData.modules.length;i ++){
                    var result = this.expandModule(moduleData, moduleData.modules[i].id);
                    if(result.isError){
                        return e.error(result);
                    }
                }
                return moduleData;
            },
            // Locates and returns module by its Id;
            findModule: function(moduleData, moduleId){
                var e = errorFact.create("moduleFact.findModule", arguments);
                if(!moduleData || !moduleData.modules || !moduleId){
                    return e.error("Invalid parameters passed");
                }
                for(var i = 0;i < moduleData.modules.length;i ++){
                    if(moduleData.modules[i].id == moduleId){
                        return moduleData.modules[i];
                    }
                }
                return e.error("Module with Id = " + moduleId + " not found");
            },
            // Locates and returns Hint Group by its Id;
            findHintGroup: function(moduleData, hintGroupId){
                var e = errorFact.create("moduleFact.findHintGroup", arguments);
                if(!moduleData || !moduleData.hintGroups || !hintGroupId){
                    return e.error("Invalid parameters passed");
                }
                for(var i = 0;i < moduleData.hintGroups.length;i ++){
                    if(moduleData.hintGroups[i].id == hintGroupId){
                        return moduleData.hintGroups[i];
                    }
                }
                return e.error("Hint Group with Id = " + hintGroupId + " not found");
            }
        }
    }]);
    

    So, when you have such factory in place, your high-level code, such as in a controller would just log any issues as shown in the example below:

        "use strict";
    
    app.controller('standardsCtrl', ['$scope', 'moduleFact', function($scope, moduleFact){
    
            var data = ...//getting data;
            var mod = moduleFact.expandAllModules(data);
            if(mod.isError){
                mod.log(); // log all error details into the console;
            }else{
                // use the data
            }
        });
    
    }]);
    
    0 讨论(0)
  • 2021-01-31 04:06

    whats your opinion to create a centralized error handling function for your app

    so whenever an error happened with your frontend tear (angular, API calls,...) it executed, so no need to write your error handling every time

    so here is my code

    (function () {
        'use strict';
        angular
            .module('app')
            .factory('$exceptionHandler', ExceptionHandler);
    
        ExceptionHandler.$inject = ['$injector']; //for minification 
    
        function ExceptionHandler($injector) {
            var $log, sweetAlert, $translate;
    
            return function exceptionHandler(exception, cause) {
                // Add DI here to prevent circular dependency
                $log = $log || $injector.get('$log');
                sweetAlert = sweetAlert || $injector.get('sweetAlert'); //19degrees.ngSweetAlert2
                $translate = $translate || $injector.get('$translate');
                // $loggerService = $loggerService || $injector.get('$loggerService');
    
                var title, message;
                title = $translate.instant('General error title');
                message = $translate.instant('General error message', { exceptionMessage: exception.message });
                sweetAlert.error(title, message);
    
                $log.error(exception, cause);
                // loggerService.logErrorsToBackend(exception, cause);
            };
        }
    })();
    

    I'm not sure if this approach considered to be a best practice but hope it helps you.

    0 讨论(0)
提交回复
热议问题