2016-05-25 12 views
9

Ich möchte bestimmte $http Fehlerszenarien global abfangen, die verhindern, dass Controller die Fehler selbst behandeln. Ich denke, dass ich einen HTTP-Interceptor brauche, aber ich bin mir nicht sicher, wie ich meine Controller davon abbringen kann, auch den Fehler zu behandeln.

Ich habe einen Controller wie folgt aus:

function HomeController($location, $http) { 
    activate(); 

    function activate() { 
     $http.get('non-existent-location') 
      .then(function activateOk(response) { 
       alert('Everything is ok'); 
      }) 
      .catch(function activateError(error) { 
       alert('An error happened'); 
      }); 
    } 
} 

Und eine HTTP Interceptor wie folgt aus:

function HttpInterceptor($q, $location) { 
    var service = { 
     responseError: responseError 
    }; 

    return service; 

    function responseError(rejection) { 
     if (rejection.status === 404) { 
      $location.path('/error'); 
     } 
     return $q.reject(rejection); 
    } 
} 

Dies funktioniert, so viel in wie der Browser auf die '/ Fehler' Pfad umleitet. Aber das Versprechen zu fangen in HomeController ist auch Ausführen, und ich will das nicht.

Ich weiß, ich könnte HomeController so codieren, dass es einen 404-Fehler ignoriert, aber das ist nicht wartbar. Angenommen, ich ändere HttpInterceptor, um auch 500 Fehler zu behandeln, dann müsste ich wieder HomeController ändern (ebenso wie alle anderen Controller, die seither hinzugefügt wurden, die $http verwenden). Gibt es eine elegantere Lösung?

+0

Ich habe mich das gefragt, aber dann habe ich es vergessen. Danke, dass du mich daran erinnerst, es herauszufinden: D –

Antwort

17
app.js

Option 1 - Pause/ca ncel die Versprechenskette

Eine kleine Änderung im HttpInterceptor kann dazu dienen, die Versprechenskette zu brechen/abzubrechen, was bedeutet, dass weder activateOk noch activateError auf dem Controller ausgeführt werden.

function HttpInterceptor($q, $location) { 
    var service = { 
     responseError: responseError 
    }; 

    return service; 

    function responseError(rejection) { 
     if (rejection.status === 404) { 
      $location.path('/error'); 
      return $q(function() { return null; }) 
     } 
     return $q.reject(rejection); 
    } 
} 

Die Linie return $q(function() { return null; }), bricht das Versprechen.

Ob dies "ok" ist, ist ein Thema der Debatte. Kyle Simpson in „You don't know JS“ heißt es:

Viele Versprechen Abstraktion Bibliotheken Einrichtungen bieten Versprechen zu stornieren, aber das ist eine schreckliche Idee!Viele Entwickler wünschen Promises hatte ursprünglich mit externen Stornierungsfähigkeit entwickelt worden, aber das Problem ist, dass es einen Verbraucher/Beobachter eines Promise beeinflussen könnte einige andere Verbraucher die Fähigkeit, das gleiche Versprechen zu beobachten. Dies verstößt gegen die Vertrauenswürdigkeit des future-value (externe Unveränderlichkeit), aber morever ist die Verkörperung der "Fernwirkung" anti-Muster ...

gut? Schlecht? Wie gesagt, es ist ein Diskussionsthema. Ich mag die Tatsache, dass es keine Änderungen an bestehenden $http Verbraucher erfordert.

Kyle hat ganz recht, wenn er sagt:

Viele Versprechen Abstraktion Bibliotheken Einrichtungen bieten Versprechen zu stornieren ...

Die Drossel Versprechen Bibliothek zum Beispiel hat die Unterstützung für den Abbruch. Von the documentation:

Die neue Stornierung hat „do not care“ Semantik, während die alte Löschung Semantik hatte abbrechen. Das Abbrechen eines Versprechens bedeutet einfach , dass seine Handler-Callbacks nicht aufgerufen werden.

Option 2 - Eine andere Abstraktion

Promises sind eine relativ breite Abstraktion. Vom Promises/A+ specification:

Ein Versprechen stellt das schließliche Ergebnis eines asynchronen Betrieb.

Der Winkel $http Dienst verwendet die $q Umsetzung der Versprechen ein Versprechen für das schließliche Ergebnis einer asynchronen HTTP-Anforderung zurückzukehren.

Es lohnt sich nichts, was $httptwo deprecated functions, .success und .error, die das zurückgegebene Versprechen dekorieren hat. Diese Funktionen waren veraltet, weil sie nicht in der typischen Weise verkettet werden konnten, die Versprechungen sind, und es wurde angenommen, dass sie nicht viel Wert als eine "HTTP-spezifische" Menge von Funktionen hinzufügen.

Aber das soll nicht heißen, dass wir nicht unsere eigene HTTP-Abstraktion/Wrapper erstellen können, die nicht einmal das zugrundeliegende Versprechen offen legt, das von $http verwendet wird. Wie folgt aus:

function HttpWrapper($http, $location) { 
    var service = { 
     get: function (getUrl, successCallback, errorCallback) { 
      $http.get(getUrl).then(function (response) { 
       successCallback(response); 
      }, function (rejection) { 
       if (rejection.status === 404) { 
        $location.path('/error'); 
       } else { 
        errorCallback(rejection); 
       } 
      }); 
     } 
    }; 

    return service; 
} 

sein, dass dies nicht ein Versprechen nicht zurückgibt, muss der Verbrauch ein wenig anders arbeiten:

HttpWrapper.get('non-existent-location', getSuccess, getError); 

function getSuccess(response) { 
    alert('Everything is ok'); 
} 

function getError(error) { 
    alert('An error happened'); 
} 

Im Falle eines 404 wird die Lage geändert zu " Fehler ', und weder getSuccess noch getError Callbacks werden ausgeführt.

Diese Implementierung bedeutet, dass die Möglichkeit, HTTP-Anfragen zu ketten, nicht mehr verfügbar ist. Ist das ein akzeptabler Kompromiss? Ergebnisse können variieren...

Option 3 - Verzieren die Ablehnung

Credit TJ für seinen Kommentar:

, wenn Sie die Fehlerbehandlung in einem bestimmten Controller benötigen, werden Sie Bedingungen benötigen ein Fehler zu überprüfen, ob behandelt wurde in Interceptor/Service etc.

Der HTTP Interceptor kann das Versprechen Ablehnung mit einer Eigenschaft handled dekorieren, ob i, um anzuzeigen, t's hat den Fehler behandelt.

function HttpInterceptor($q, $location) { 
    var service = { 
     responseError: responseError 
    }; 

    return service; 

    function responseError(rejection) { 
     if (rejection.status === 404) { 
      $location.path('/error'); 
      rejection.handled = true; 
     } 

     return $q.reject(rejection); 
    } 
} 

dann Regler wie folgt aussieht:

$http.get('non-existent-location') 
    .then(function activateOk(response) { 
     alert('Everything is ok'); 
    }) 
    .catch(function activateError(error) { 
     if (!error.handled) { 
      alert('An error happened'); 
     } 
    }); 

Zusammenfassung

Anders als Option 2 Option 3 bleibt aber die Möglichkeit für jeden $http Verbraucher Kette verspricht, was sich positiv in dem Sinne ist, dass es die Funktionalität nicht eliminiert.

Beide Optionen 2 und 3 haben weniger "Fernwirkung". Im Fall von Option 2 macht die alternative Abstraktion deutlich, dass sich die Dinge anders verhalten als die übliche Implementierung. Und für Option 3 erhält der Verbraucher das Versprechen, mit ihm zu tun, wie es ihm gefällt.

Alle 3 Optionen erfüllen die Wartbarkeitskriterien, da Änderungen an der globalen Fehlerbehandlungsroutine für mehr oder weniger Szenarien keine Änderungen an den Konsumenten erfordern.

+0

Schön. Gut geschriebene Antwort –

+1

Ich mag den Schnitt Ihres Jib. – Zach

3

Wrap $http in Ihrem eigenen Dienst. Auf diese Weise müssen Sie, wenn Sie die Fehlerbehandlungslogik ändern müssen, nicht alle Controller ändern.

Etwas wie:

angular.module('test') 
    .factory('http', ['$http', 
     function(http) { 
     return { 
      get: function(getUrl) { 
      return http.get(getUrl).then(function(response) { 
       return response; 
      }, function() { 
       //handle errors here 
      }); 
      }, 
      post: function(postUrl, data) { 
       return http.post(postUrl, data).then(function(response) { 
       return response; 
       }, function() { 
       //handle errors here 
       }); 
      } 
      // other $http wrappers 
     }; 
     }); 
+0

Wenn ich in meinem Beispiel "$ http" für "htt" ersetze, zeigt die Seite eine Warnung "Alles ist in Ordnung" und leitet auf die Fehlerseite um. Der Parameter 'response' der Funktion 'activateOk' ist 'undefined'. Dies ist kein günstiges Ergebnis. – Snixtor

+0

@Snixtor Ihr Beispiel trifft eine ungültige URL, so dass 'activateOk' niemals aufgerufen wird. Es wird 'activateError' sein, das aufgerufen wird. Meine Antwort ist keine Kopierpastenlösung. Es gibt einen Kommentar '// handle errors here', du musst deine Logik wie die zum Ignorieren von' 404' hinzufügen, die von Interceptor behandelt wird. Sobald Sie einen Service wie diesen haben, brauchen Sie wahrscheinlich keinen Fehlerhandler im Controller, wenn Sie jemals einen benötigen, dann sollten Sie die Fehlerantwort zurückgeben. Das liegt an dir. –

+0

Sorry, ich bin nicht sicher, was du mit "ungültiges Beispiel" meinst. Das Ergebnis, das ich beschrieben habe, ist, was passiert, wenn ich '$ location.path ('/ error');' anstelle von "handle errors here" einstelle. Wenn es in "handle errors here" keine Versprechensverweigerung oder -auflösung gibt, wird das Versprechen aufgelöst, wobei "response" nicht definiert ist. – Snixtor

1
application= angular.module('yourmodule', yourdependencies) ;  
application.config(["$provide", "$httpProvider", 
     function (provide, httpProvider){  
      registerInterceptors(provide, httpProvider); 
     }]); 

registerhttpInterceptor = function (provide, httpProvider) { 
       provide.factory("appHttpInterceptor", [ 
        function() { 
         return { 
          responseError: function (rejection) { 
           if (rejection.status == 401) { 
            return "Your custom response"; 
           } 
          } 
         }; 
        } 
       ]); 
       httpProvider.interceptors.push('appHttpInterceptor'); 
      }; 
+0

Ich glaube nicht, dass Sie vollständig verstanden haben, was ich erreichen möchte. Wenn Sie "Your custom response" von "responseError" zurückgeben, bedeutet dies, dass die Fehlerbehandlung immer noch in der Verantwortung des Controllers liegt. – Snixtor

+0

Nein, dieser Code kann global existieren, Sie binden ihn an das Modul, Sie müssen nur "$ provide" und "$ httpProvider" einfügen und ausführen, wenn das Modul initialisiert wird –

+0

Lokale vs globale Präsenz ist nicht das Problem zur Hand. Ihr Beispiel wird bedeuten, dass die Versprechen "$ http" im Controller mit einem Objekt "Ihre benutzerdefinierte Antwort" aufgelöst wird, für die dann der * Controller * zuständig sein würde. Ich möchte nicht, dass der Controller für irgendetwas verantwortlich ist, was den Fehler betrifft. Um es anders zu betrachten, sollten weder 'activateOk' noch' activateError' ausgeführt werden. – Snixtor

1

This article erklärt, wie Sie http Anrufe abfangen und verschiedene Operationen darauf ausführen. Einer von ihnen behandelt Fehler.

Ein schnelles Kopieren und Einfügen aus dem obigen Artikel ...

app.config(function($httpProvider) { 
    $httpProvider.interceptors.push(function($q) { 
    return { 
     // This is the responseError interceptor 
     responseError: function(rejection) { 
     if (rejection.status > 399) { // assuming that any code over 399 is an error 
      $q.reject(rejection) 
     } 

     return rejection; 
     } 
    }; 
    }); 
}); 
+0

Die Verwendung von 'userService' ist in diesem Fall eine interessante Studie, die die Ablehnung des '$ http'-Versprechens verzögert, bis sie erfolgreich gelöst werden kann (der Benutzer meldet sich an). Aber ansonsten wird nur das Versprechen abgelehnt und der Controller wird angewiesen, den Fehler zu behandeln. Dies spricht nicht an, was ich zu erreichen versuche. – Snixtor

+0

Ich nahm an, dass Sie die Essenz aus dem Artikel und dem Code genommen haben. Ich kann den Code entfernen, der für den Umgang mit dem Fehler nicht relevant ist –

+0

Ich verstehe die Essenz des Artikels, aber es behandelt nicht das Szenario, das ich beschreibe. Der Titel des Artikels ist eigentlich ganz passend: "80/20 guide". Ich würde mein Szenario in den 20% sehen, nicht die 80% :) – Snixtor

0

Versuchen Sie diese

Fabrik/golbalErrorHandler.js

yourmodule.factory('golbalErrorHandler', [function() { 
     var golbalError= { 
      responseError: function(response) { 
       // Golbal Error Handling 
       if (response.status != 200){ 
        console.error(response); 
       } 
      } 
     } 
     return golbalError; 

     }]); 

yourmodule.config(['$httpProvider', function($httpProvider) { 
     $httpProvider.interceptors.push('golbalErrorHandler'); 
    }]); 
+0

Ich bin ziemlich neu in Angular, was ist die Verwendung von Rückgabewert 'GolbalError'? –

Verwandte Themen