web-dev-qa-db-fra.com

Pratique recommandée pour la gestion des exceptions d'application dans AngularJS

J'explore actuellement les méthodes possibles pour gérer les exceptions d'application dans AngularJS.

Une des choses que nous voulions vraiment éviter était de placer plusieurs parties de l'application dans des blocs try/catch imbriqués, mais de gérer les choses proprement, c'est-à-dire de lever une exception en réponse à une promesse.

  • Quelqu'un at-il déjà abordé cette question et formulé des recommandations?
  • Toutes les suggestions sur la façon de collecter les exceptions dans les services ainsi que les contrôleurs/directives. (Voir ci-dessous - la diffusion fonctionne bien, mais uniquement si vous pouvez attacher un auditeur à une étendue).

Progrès accomplis jusqu'à présent

Quelques objectifs de conception courts:

  • Autoriser le traitement des exceptions d'une partie de l'application ailleurs - ou éventuellement de plusieurs emplacements (par exemple, "notification d'erreur à l'utilisateur", "widget de désactivation").
  • Fournit une gestion centralisée des conditions d’erreur courantes - c'est-à-dire se connecter au serveur, afficher une notification à l'utilisateur, rediriger vers une connexion.
  • Autoriser les exceptions provenant de contrôleurs, directives, services, etc.
  • Finalement, autoriser les messages localisés.

L’idée actuelle de mon équipe est d’écrire un service pour gérer les exceptions, ce qui exposerait une gamme d’appels simples:

exceptionService.warn('exception_token');

exceptionService.crit('another_exception_token');

Ce service formaterait alors un objet "exception" et le diffuserait à partir du rootscope. Cela permettrait à un gestionnaire par défaut de surveiller toutes les émissions et d'appliquer des actions par défaut, ainsi que de définir des écouteurs personnalisés dans d'autres portées, ce qui pourrait gérer des conditions plus spécifiques, à savoir désactiver une partie de l'interface utilisateur.

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

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

Je pensais à la même chose récemment, et je me suis rendu compte que s’il s’agissait d’une bonne gestion des erreurs en javascript, le cadre utilisé, Angular, n’était pas pertinent. J'ai récemment écrit un tel gestionnaire d'erreurs pour un projet AngularJS, mais je l'ai fait de manière à ce qu'il puisse être utilisé dans n'importe quel cadre.

Voici le code complet. Vous pouvez soit l'utiliser directement, soit le modifier selon vos besoins ...

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

Ci-dessous une usine qui montre comment il est utilisé:

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

Ainsi, lorsque vous avez une telle usine en place, votre code de haut niveau, tel que dans un contrôleur, ne ferait que consigner tout problème, comme indiqué dans l'exemple ci-dessous:

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

}]);
4
vitaly-t

Vous pouvez écraser $ exceptionHandler afin de transmettre les exceptions à votre propre service central pour les exceptions, mais $ exceptionHandler semble ne recevoir que les exceptions émises par vos contrôleurs, directives, etc., mais pas pour les exceptions provenant d'appels ajax . Pour ces exceptions, vous pouvez implémenter un intercepteur tel que celui décrit dans cette page:

EDITED: Link est mort en permanence.
Archive.org lien

3
Christian Quirós

quel est votre avis pour créer un centralized error handling function pour votre application 

donc chaque fois qu'une erreur est survenue avec votre déchirure frontale (angulaire, appels d'API, ...) elle s'est exécutée, donc no need to write votre error handlingevery time 

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

Je ne sais pas si cette approche est considérée comme une pratique exemplaire, mais j'espère que cela vous aidera.

1
Basheer AL-MOMANI