Подтвердить что ты не робот

Рекомендуемая практика обработки исключений приложений в AngularJS

В настоящее время я изучаю возможные методы обработки исключений приложений в AngularJS.

Одна из вещей, которую мы действительно хотели избежать, заключалась в том, чтобы обернуть несколько частей приложения во вложенные блоки try/catch, но обрабатывать вещи чисто - i.e бросать исключение в ответ на обещание.

  • Кто-нибудь рассматривал этот вопрос раньше и имел какие-либо рекомендации?
  • Любые предложения о том, как выбирать исключения в службах, а также контроллеры/директивы. (См. Ниже - широковещание работает нормально, но только если вы можете подключить слушателя к области).

Прогресс до сих пор

Несколько коротких целей дизайна:

  • Разрешить исключения из одной части приложения, которые будут обрабатываться в другом месте, или, возможно, в нескольких местах (например, "отображать уведомление об ошибке пользователю", "отключить виджет" ).
  • Обеспечьте централизованное управление общими условиями ошибки - то есть зайдите на сервер, отобразите уведомление пользователю, переадресовывайте логин.
  • Разрешить исключения из контроллеров, директив, служб и т.д.
  • В конечном итоге разрешить локализованные сообщения.

Текущая склонность моей команды состоит в том, чтобы написать службу для обработки исключений, которая вывела бы ряд простых вызовов:

exceptionService.warn('exception_token');

exceptionService.crit('another_exception_token');

Затем эта служба будет форматировать объект "исключение" и транслировать это из корнеплода. Это позволит обработчику по умолчанию отслеживать любые широковещательные передачи и применять действия по умолчанию, а также настраивать пользовательские слушатели в других областях, которые могут обрабатывать более конкретные условия - то есть отключить часть пользовательского интерфейса.

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

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

Ответ 1

Я думал об этом недавно, и мне пришло в голову, что когда дело доходит до хорошей обработки ошибок в 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 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 reserved variable called arguments, which
  provides reference to all of the function 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 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;
        }
    }
});

Ниже представлен factory, который показывает, как он используется:

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

Итак, когда у вас есть такой factory, ваш высокоуровневый код, например, в контроллере, будет регистрировать любые проблемы, как показано в следующем примере:

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

}]);

Ответ 2

Вы можете переопределить $exceptionHandler, чтобы передать исключения из вашей собственной центральной службы для исключений, но $exceptionHandler, похоже, получает только исключения, выписанные из ваших контроллеров, директив и т.д.... но не для исключений от вызовов ajax. Для этих исключений вы можете реализовать перехватчик, подобный описанному на этой странице:

EDITED: ссылка мертва навсегда.
Ссылка Archive.org

Ответ 3

Каково ваше мнение о создании centralized error handling function для вашего приложения.

поэтому всякий раз, когда произошла ошибка с нарушением интерфейса (angular, вызовы 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);
        };
    }
})();

Я не уверен, что этот подход считается лучшей практикой, но надеюсь, что это поможет вам.