wake-up-neo.net

fehlerbehandlung bei asynchronen Aufrufen von node.js

Ich bin neu in node.js, obwohl ich mit JavaScript im Allgemeinen ziemlich vertraut bin. Meine Frage bezieht sich auf "Best Practices", wie Fehler in node.js behandelt werden sollen.

Normalerweise verwende ich beim Programmieren von Webservern, FastCGI-Servern oder Webseiten in verschiedenen Sprachen Ausnahmen mit blockierenden Handlern in einer Multithreading-Umgebung. Wenn eine Anfrage eingeht, mache ich normalerweise so etwas:

function handleRequest(request, response) {
  try {

    if (request.url=="whatever")
      handleWhateverRequest(request, response);
    else
      throw new Error("404 not found");

  } catch (e) {
    response.writeHead(500, {'Content-Type': 'text/plain'});
    response.end("Server error: "+e.message);
  }
}

function handleWhateverRequest(request, response) {
  if (something) 
    throw new Error("something bad happened");
  Response.end("OK");
}

Auf diese Weise kann ich immer mit internen Fehlern umgehen und eine gültige Antwort an den Benutzer senden.

Ich verstehe, dass man mit node.js nicht blockierende Aufrufe ausführen soll, was offensichtlich zu einer unterschiedlichen Anzahl von Rückrufen führt, wie in diesem Beispiel:

var sys    = require('sys'),
    fs     = require('fs');

require("http").createServer(handleRequest).listen(8124);

function handleRequest(request, response) {

  fs.open("/proc/cpuinfo", "r",
    function(error, fd) {
      if (error)
        throw new Error("fs.open error: "+error.message);

      console.log("File open.");

      var buffer = new require('buffer').Buffer(10);
      fs.read(fd, buffer, 0, 10, null,
        function(error, bytesRead, buffer) {

          buffer.dontTryThisAtHome();  // causes exception

          response.end(buffer);
        }); //fs.read

    }); //fs.open

}

In diesem Beispiel wird der Server vollständig abgebrochen, da keine Ausnahmen abgefangen werden .. Mein Problem ist, dass ich keinen einzigen Try/Catch mehr verwenden kann und daher im Allgemeinen keine Fehler auffangen kann, die während der Verarbeitung auftreten können der Anfrage. 

Natürlich könnte ich in jedem Callback einen try/catch hinzufügen, aber ich mag diesen Ansatz nicht, weil der Programmierer dann die Aufgabe hat, dass er nicht try/catch versucht. Für einen komplexen Server mit vielen unterschiedlichen und komplexen Handlern ist dies nicht akzeptabel.

Ich könnte einen globalen Ausnahmebehandler verwenden (der den vollständigen Serverabsturz verhindert), aber ich kann dem Benutzer keine Antwort senden, da ich nicht weiß, welche Anforderung zu der Ausnahme führt. Dies bedeutet auch, dass die Anfrage nicht bearbeitet/geöffnet bleibt und der Browser für immer auf eine Antwort wartet.

Hat jemand eine gute, grundsolide Lösung?

45
Udo G

Knoten 0.8 führt ein neues Konzept mit dem Namen "Domains" ein. Sie entsprechen in etwa der AppDomains in .net und bieten eine Möglichkeit, eine Gruppe von IO -Operationen zu kapseln. Sie ermöglichen Ihnen grundsätzlich, Ihre Anfragen zur Verarbeitung der Anfrage in eine kontextspezifische Gruppe zu integrieren. Wenn diese Gruppe nicht abgefangene Ausnahmen auslöst, können sie so behandelt und behandelt werden, dass Sie Zugriff auf alle erforderlichen bereichs- und kontextabhängigen Informationen erhalten, um den Fehler (falls möglich) erfolgreich beheben zu können.

Diese Funktion ist neu und wurde erst vor kurzem eingeführt, also mit Vorsicht, aber soweit ich das beurteilen kann, wurde sie speziell eingeführt, um das Problem zu lösen, mit dem das OP zu kämpfen hat.

Dokumentation finden Sie unter: http://nodejs.org/api/domain.html

13
Sam Shiles

Dies ist momentan eines der Probleme mit Node. Es ist praktisch unmöglich festzustellen, durch welche Anforderung ein Fehler in einen Rückruf geworfen wurde. 

Sie müssen Ihre Fehler innerhalb der Rückrufe selbst behandeln (wo Sie noch einen Verweis auf die Anforderungs- und Antwortobjekte haben), wenn möglich. Der uncaughtException-Handler beendet das Beenden des Knotenprozesses, aber die Anforderung, die die Ausnahme an erster Stelle verursacht hat, wird aus Sicht des Benutzers nur dort hängen bleiben.

5
chjj

Überprüfen Sie den uncaughtException-Handler in node.js. Es erfasst die ausgelösten Fehler, die bis zur Ereignisschleife blubbern.

http://nodejs.org/docs/v0.4.7/api/process.html#event_uncaughtException_

Aber keine Fehler zu werfen ist immer eine bessere Lösung. Sie könnten einfach eine return res.end('Unabled to load file xxx'); machen

5
3rdEden

Ich antworte auf meine eigene Frage ... :)

Wie es scheint, gibt es keine Möglichkeit, Fehler manuell abzufangen. Ich benutze jetzt eine Hilfsfunktion, die selbst eine function zurückgibt, die einen try/catch-Block enthält. Außerdem prüft meine eigene Webserverklasse, ob entweder die Anforderungsbehandlungsfunktion response.end() oder die try/catch-Hilfsfunktion waitfor() aufruft (andernfalls eine Ausnahme). Dadurch wird weitestgehend vermieden, dass Anforderungen vom Entwickler irrtümlicherweise nicht geschützt werden. Es ist keine 100% fehleranfällige Lösung, funktioniert aber gut genug für mich.

handler.waitfor = function(callback) {
  var me=this;

  // avoid exception because response.end() won't be called immediately:
  this.waiting=true;

  return function() {
    me.waiting=false;
    try {
      callback.apply(this, arguments);

      if (!me.waiting && !me.finished)
        throw new Error("Response handler returned and did neither send a "+
          "response nor did it call waitfor()");

    } catch (e) {
      me.handleException(e);
    }
  }
}

Auf diese Weise muss ich nur einen Inline-Aufruf waitfor() hinzufügen, um auf der sicheren Seite zu sein.

function handleRequest(request, response, handler) {
  fs.read(fd, buffer, 0, 10, null, handler.waitfor(
    function(error, bytesRead, buffer) {

      buffer.unknownFunction();  // causes exception
      response.end(buffer);
    }
  )); //fs.read
}

Der eigentliche Überprüfungsmechanismus ist etwas komplexer, aber es sollte klar sein, wie er funktioniert. Wenn jemand interessiert ist, kann ich den vollständigen Code hier posten.

3
Udo G

Sehr gute Frage Ich habe jetzt das gleiche Problem. Der beste Weg wäre wahrscheinlich uncaughtException. Der Verweis auf Respone- und Request-Objekte ist nicht das Problem, da Sie sie in Ihr Exception-Objekt einschließen können, das an das Ereignis uncaughtException übergeben wird. Etwas wie das:

var HttpException = function (request, response, message, code) {

  this.request = request;
  this.response = response;  
  this.message = message;    
  this.code = code || 500;

}

Werfen Sie es:

throw new HttpException(request, response, 'File not found', 404);

Und handle die Antwort:

process.on('uncaughtException', function (exception) {
  exception.response.writeHead(exception.code, {'Content-Type': 'text/html'});
  exception.response.end('Error ' + exception.code + ' - ' + exception.message);
});

Ich habe diese Lösung noch nicht getestet, aber ich sehe nicht den Grund, warum dies nicht funktionieren konnte.

3
Marian Galik

Eine Idee: Sie können einfach eine Hilfsmethode verwenden, um Ihre Rückrufe zu erstellen und es zu Ihrer Standardmethode zu machen. Dies belastet den Entwickler zwar immer noch, aber zumindest können Sie Ihre Callbacks auf "normale" Weise so behandeln, dass die Wahrscheinlichkeit, einen zu vergessen, gering ist:

var callWithHttpCatch = function(response, fn) {
    try {
        fn && fn();
    }
    catch {
        response.writeHead(500, {'Content-Type': 'text/plain'}); //No
    }
}

<snipped>
      var buffer = new require('buffer').Buffer(10);
      fs.read(fd, buffer, 0, 10, null,
        function(error, bytesRead, buffer) {

          callWithHttpCatch(response, buffer.dontTryThisAtHome());  // causes exception

          response.end(buffer);
        }); //fs.read

    }); //fs.open

Ich weiß, dass dies wahrscheinlich nicht die Antwort ist, nach der Sie gesucht haben, aber eine der schönen Sachen über ECMAScript (oder die Funktionsprogrammierung im Allgemeinen) ist, wie einfach Sie Ihre eigenen Werkzeuge für solche Dinge einsetzen können.

2
jslatts

Zum jetzigen Zeitpunkt sehe ich hier "Versprechen".

http://howtonode.org/promises
https://www.promisejs.org/

Dadurch können Code und Callbacks für die Fehlerverwaltung gut strukturiert werden und sind außerdem lesbarer. __ Sie verwenden hauptsächlich die Funktion .then ().

someFunction().then(success_callback_func, failed_callback_func);

Hier ist ein grundlegendes Beispiel:

  var SomeModule = require('someModule');

  var success = function (ret) {
      console.log('>>>>>>>> Success!');
  }

  var failed = function (err) {
    if (err instanceof SomeModule.errorName) {
      // Note: I've often seen the error definitions in SomeModule.errors.ErrorName
      console.log("FOUND SPECIFIC ERROR");
    }
    console.log('>>>>>>>> FAILED!');
  }

  someFunction().then(success, failed);
  console.log("This line with appear instantly, since the last function was asynchronous.");
1
SilentSteel

Bei der synchronen Multithread-Programmierung (z. B. .NET, Java, PHP) können Sie keine aussagekräftigen Informationen an den Client zurückgeben, wenn eine benutzerdefinierte unbekannte Ausnahme erfasst wird. Sie können HTTP 500 einfach zurückgeben, wenn Sie keine Informationen zur Ausnahme haben.

Das "Geheimnis" besteht also darin, ein beschreibendes Error-Objekt zu füllen. Auf diese Weise kann Ihr Error-Handler den aussagekräftigen Fehler auf den richtigen HTTP-Status und optional ein beschreibendes Ergebnis abbilden. Sie müssen jedoch auch die Ausnahme abfangen, bevor sie bei process.on ('uncaughtException') ankommt:

Schritt 1: Definieren Sie ein aussagekräftiges Fehlerobjekt

function appError(errorCode, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.errorCode = errorCode;
    //...other properties assigned here
};

appError.prototype.__proto__ = Error.prototype;
module.exports.appError = appError;

Schritt 2: Wenn Sie eine Ausnahme auslösen, füllen Sie sie mit Eigenschaften (siehe Schritt 1), die es dem Handler ermöglicht, diese in ein HTTP-Ergebnis von meannigul zu konvertieren:

throw new appError(errorManagement.commonErrors.resourceNotFound, "further explanation", true)

Schritt 3: Wenn Sie potenziell gefährlichen Code aufrufen, fangen Sie Fehler ab, und werfen Sie den Fehler erneut aus, während Sie zusätzliche kontextbezogene Eigenschaften im Error-Objekt eingeben

Schritt 4: Sie müssen die Ausnahme während der Bearbeitung der Anforderung abfangen. Dies ist einfacher, wenn Sie einige führende Versprechenbibliotheken verwenden (BlueBird ist großartig), mit denen Sie asynchrone Fehler abfangen können. Wenn Sie keine Versprechungen verwenden können, gibt eine integrierte NODE-Bibliothek Fehler beim Rückruf zurück.

Schritt 5: Nachdem Ihr Fehler nun erfasst wurde und beschreibende Informationen zu den Vorgängen enthält, müssen Sie ihn nur noch einer sinnvollen HTTP-Antwort zuordnen. Der schöne Teil hier ist, dass Sie möglicherweise einen zentralisierten Fehlerhandler haben, der alle Fehler abruft und diese der HTTP-Antwort zuordnet:

    //this specific example is using Express framework
    res.status(getErrorHTTPCode(error))
function getErrorHTTPCode(error)
{
    if(error.errorCode == commonErrors.InvalidInput)
        return 400;
    else if...
}

Sie können andere verwandte Best Practices hier

0
Yonatan

Zwei Dinge haben mir wirklich geholfen, dieses Problem in meinem Code zu lösen.

  1. Das 'longjohn'-Modul, mit dem Sie die vollständige Stack-Ablaufverfolgung sehen können (über mehrere asynkrone Callbacks).
  2. Eine einfache Verschlusstechnik, um Ausnahmen innerhalb des Standard-Idioms callback(err, data) zu halten (hier in CoffeeScript dargestellt).

    ferry_errors = (callback, f) ->
      return (a...) ->
        try f(a...)
        catch err
          callback(err)
    

Jetzt können Sie unsicheren Code umbrechen, und Ihre Rückrufe behandeln Fehler auf dieselbe Weise: Überprüfen Sie das Fehlerargument.

0
Jesse Dailey

Ich habe vor kurzem eine einfache Abstraktion namens WaitFor erstellt, um async-Funktionen im Synchronisationsmodus (basierend auf Fasern) aufzurufen: https://github.com/luciotato/waitfor

Es ist zu neu, um "steinhart" zu sein. 

mit wait.for können Sie die async-Funktion wie eine Synchronisation verwenden, ohne die Ereignisschleife des Knotens zu blockieren. Es ist fast so, wie Sie es gewohnt sind:

var wait=require('wait.for');

function handleRequest(request, response) {
      //launch fiber, keep node spinning
      wait.launchFiber(handleinFiber,request, response); 
}

function handleInFiber(request, response) {
  try {
    if (request.url=="whatever")
      handleWhateverRequest(request, response);
    else
      throw new Error("404 not found");

  } catch (e) {
    response.writeHead(500, {'Content-Type': 'text/plain'});
    response.end("Server error: "+e.message);
  }
}

function handleWhateverRequest(request, response, callback) {
  if (something) 
    throw new Error("something bad happened");
  Response.end("OK");
}

Da Sie sich in einer Glasfaser befinden, können Sie nacheinander "Blockieren der Faser" programmieren, nicht jedoch die Ereignisschleife des Knotens.

Das andere Beispiel:

var sys    = require('sys'),
    fs     = require('fs'),
    wait   = require('wait.for');

require("http").createServer( function(req,res){
      wait.launchFiber(handleRequest,req,res) //handle in a fiber
  ).listen(8124);

function handleRequest(request, response) {
  try {
    var fd=wait.for(fs.open,"/proc/cpuinfo", "r");
    console.log("File open.");
    var buffer = new require('buffer').Buffer(10);

    var bytesRead=wait.for(fs.read,fd, buffer, 0, 10, null);

    buffer.dontTryThisAtHome();  // causes exception

    response.end(buffer);
  }
  catch(err) {
    response.end('ERROR: '+err.message);
  }

}

Wie Sie sehen, habe ich wait.for verwendet, um die asynchronen Funktionen des Knotens im .c. -Ziel ohne (sichtbare) Rückrufe aufzurufen, sodass ich den gesamten Code in einem try-catch-Block haben kann.

wait.for löst eine Ausnahme aus, wenn eine der asynchronen Funktionen den Wert err! == null zurückgibt

weitere Informationen unter https://github.com/luciotato/waitfor

0
Lucio M. Tato