2013-03-05 16 views
32

Attualmente sto esplorando i possibili metodi per gestire le eccezioni a livello di applicazione in AngularJS.Esercizi consigliati per la gestione delle eccezioni dell'applicazione in AngularJS

Una delle cose che volevamo davvero evitare era il wrapping di più parti dell'applicazione in blocchi nidificati di try/catch, ma gestire le cose in modo pulito, cioè creare un'eccezione in risposta a una promessa.

  • Qualcuno ha già trattato questo problema e ha qualche raccomandazione?
  • Qualsiasi suggerimento su come rilevare le eccezioni nei servizi così come i controller/le direttive. (Vedi sotto - broadcast funziona bene, ma solo se è possibile collegare un listener a un ambito).

progressi finora compiuti

Alcuni obiettivi di progettazione brevi:

  • consentire eccezioni da una parte della domanda da movimentare altrove - o forse più luoghi (ad esempio 'notifica di errore di visualizzazione per utente ',' disabilita widget ').
  • Fornire la gestione centralizzata delle condizioni di errore comuni, ad esempio accedere al server, visualizzare la notifica all'utente, reindirizzare al login.
  • Consentire l'esclusione di eccezioni da controllori, direttive, servizi, ecc.
  • Consentire infine messaggi localizzati.

L'attuale pendente della mia squadra è quello di scrivere un servizio per gestire le eccezioni, che esporrebbe una serie di chiamate semplici:

exceptionService.warn('exception_token'); 

exceptionService.crit('another_exception_token'); 

Questo servizio sarebbe quindi formattare un oggetto 'un'eccezione' e trasmesso questo dal rootscope. Ciò consentirebbe a un gestore predefinito di guardare eventuali trasmissioni e applicare azioni predefinite, nonché consentire ai listener personalizzati di essere impostati in altri ambiti, che potrebbero gestire condizioni più specifiche, ovvero disabilitare una parte dell'interfaccia utente.

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

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

Hai dato un'occhiata all'oggetto $ exceptionHandler? Vedi http://stackoverflow.com/questions/13595469/how-to-override-exceptionhandler-implementation per ulteriori informazioni. –

+0

Grazie, ho dato un'occhiata. Sì - abbiamo esaminato il '$ exceptionHandler' fornito, che sulla superficie sembra funzionare bene, ma è fondamentalmente una shell per implementare la propria soluzione. La registrazione al server come menzionato in quel post è una buona idea: questa è una delle cose che vorremmo assolutamente fare. –

+0

Stiamo facendo un [app] .provider() con $ exceptionHandler in esso, principalmente per la registrazione di produzione. Non è poi così eccezionale per Dev, perché la traccia dello stack non è così buona. – Sharondio

risposta

3

È possibile sostituire il $ exceptionHandler per passare le eccezioni al proprio servizio centrale per le eccezioni, ma il $ exceptionHandler sembra ricevere solo le eccezioni generate dal tuo controller, direttive, ecc ... ma non per le eccezioni originate da chiamate Ajax. Per tali eccezioni è possibile implementare un intercettore come quello descritto in questa pagina:

MODIFICATO: il collegamento è morto in modo permanente.
Archive.org link

4

stavo pensando la stessa di recente, e mi venne in mente che quando si tratta di una buona gestione in javascript errore, è irrilevante che quadro si sta utilizzando, angolare su qualcos'altro. Recentemente ho scritto uno di questi gestori di errori per un progetto AngularJS, ma l'ho fatto in un modo che può essere utilizzato in qualsiasi framework.

Ecco il codice completo. Puoi usarlo direttamente o modificarlo secondo le tue esigenze ...

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

Qui di seguito è una fabbrica che mostra come viene utilizzato:

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

Così, quando si dispone di tale fabbrica sul posto, il codice di alto livello, come ad esempio in un controllore sarebbe solo il login qualsiasi questioni come mostrato nell'esempio seguente:

"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

che cosa è la tua opinione per creare un centralized error handling function per la vostra applicazione

così ogni volta che un errore è accaduto con la lacrima frontend (chiamate angolare, API, ...) è eseguito, in modo no need to write vostra error handlingevery time

ecco il mio codice

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

non sono sicuro se questo approccio è considerato una buona pratica, ma spero che ti aiuti.