AngularJS中应用程序异常处理的推荐做法

时间:2013-03-05 22:54:49

标签: exception-handling angularjs

我目前正在探索在AngularJS中处理应用程序范围异常的可能方法。

我们真正想要避免的一件事是将应用程序的多个部分包装在嵌套的try / catch块中,但是要干净地处理事情 - 即抛出异常以响应承诺。

  • 之前是否有人讨论过此问题并提出任何建议?
  • 有关如何在服务以及控制器/指令中获取异常的任何建议。 (见下文 - 广播工作正常,但只有你可以将一个监听器附加到一个范围)。

到目前为止的进展

一些简短的设计目标:

  • 允许应用程序的某个部分的异常在别处处理 - 或者可能是多个地方(例如“向用户显示错误通知”,“禁用小部件”)。
  • 提供常见错误条件的集中管理 - 即登录到服务器,向用户显示通知,重定向到登录。
  • 允许从控制器,指令,服务等抛出异常。
  • 最终允许本地化消息。

我团队目前的倾向是编写一个服务来处理异常,这会暴露一系列简单的调用:

exceptionService.warn('exception_token');

exceptionService.crit('another_exception_token');

然后,此服务将格式化“异常”对象并从rootscope广播。这将允许默认处理程序监视任何广播并应用默认操作,并允许在其他范围中设置自定义侦听器,这可以处理更具体的条件 - 即禁用UI的一部分。

var exception = {
    token: 'exception_token',
    severity': 'crit'
};

// broadcast exception
$rootScope.$broadcast(
'application_exception',
    exception
);

3 个答案:

答案 0 :(得分:4)

我最近在考虑同样的问题,而且我发现在javascript中进行良好的错误处理时,与使用哪个框架无关,Angular在其他方面。我最近为AngularJS项目编写了一个这样的错误处理程序,但我这样做的方式可以在任何框架中使用。

这是完整的代码。您可以直接使用它,也可以根据需要进行修改......

    /*
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;
        }
    }
});

下面是一个展示如何使用它的工厂:

    "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");
        }
    }
}]);

因此,当您拥有这样的工厂时,您的高级代码(例如在控制器中)只会记录任何问题,如下例所示:

    "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
        }
    });

}]);

答案 1 :(得分:3)

您可以覆盖$ exceptionHandler,以便将异常传递给您自己的中央服务以获取异常,但$ exceptionHandler似乎只接收从您的控制器,指令等引发的异常......但不是因为发起的异常来自ajax电话。对于这些异常,您可以实现类似本页所述的拦截器:

编辑:链接永久死亡 Archive.org link

答案 2 :(得分:1)

您的意见是为您的应用创建centralized error handling function

因此,只要您的前端撕裂(角度,API调用......)发生错误,就会执行no need to write error handling every time

所以这是我的代码

(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);
        };
    }
})();

我不确定这种方法是否被认为是最佳做法,但希望它对您有所帮助。