web-dev-qa-db-ja.com

非同期node.js呼び出しでのエラー処理

私はnode.jsを初めて使用しますが、一般的なJavaScriptにはかなり精通しています。私の質問は、node.jsでエラーを処理する方法に関する「ベストプラクティス」に関するものです。

通常、Webサーバー、FastCGIサーバー、またはWebページをさまざまな言語でプログラミングするとき、マルチスレッド環境でブロッキングハンドラーで例外を使用しています。リクエストが来たとき、私は通常このようなことをします:

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

このようにして、常に内部エラーを処理し、有効な応答をユーザーに送信できます。

Node.jsを使用すると、この例のように、さまざまなコールバックにつながるノンブロッキングコールを実行することになっていることを理解しています。

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

}

この例は、例外がキャッチされないため、サーバーを完全に強制終了します。私の問題は、単一のtry/catchを使用できなくなったため、通常、リクエストの処理中に発生する可能性のあるエラーをキャッチできないことです。

もちろん、各コールバックにtry/catchを追加することもできますが、そのアプローチは好きではありません。なぜなら、彼がforget a try/catchをしないのはプログラマ次第だからです。多くの異なる複雑なハンドラーを備えた複雑なサーバーでは、これは受け入れられません。

グローバルな例外ハンドラー(サーバーの完全なクラッシュを防ぐ)を使用できますが、どの要求が例外につながるかわからないため、ユーザーに応答を送信できません。これは、リクエストが未処理/開かれたままであり、ブラウザが応答を永久に待機していることも意味します。

誰かが優れた堅実なソリューションを持っていますか?

45
Udo G

Node 0.8は、「ドメイン」と呼ばれる新しい概念を導入します。これらは、.netのAppDomainsと非常に大まかに類似しており、IO操作のグループをカプセル化する方法を提供します。基本的に、コンテキスト固有のグループで要求処理呼び出しをラップできます。キャッチされない例外をスローすると、エラーから正常に回復するために必要なすべてのスコープおよびコンテキスト固有の情報にアクセスできる方法で処理および処理できます(可能な場合)。

この機能は新しく、導入されたばかりなので、注意して使用してください。しかし、私が伝えることができることから、OPが取り組もうとしている問題に対処するために特別に導入されました。

ドキュメントは次の場所にあります: http://nodejs.org/api/domain.html

13
Sam Shiles

これはNodeの問題の1つです。どのリクエストがコールバック内でエラーをスローしたかを追跡することは事実上不可能です。

可能であれば、コールバック自体(まだリクエストオブジェクトとレスポンスオブジェクトへの参照がある)内でエラーを処理する必要があります。 uncaughtExceptionハンドラーはノードプロセスの終了を停止しますが、そもそも例外の原因となったリクエストはユーザーの視点からそこにハングします。

5
chjj

Node.jsのuncaughtExceptionハンドラーをチェックアウトします。イベントループにバブルアップするスローされたエラーをキャプチャします。

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

ただし、エラーをスローしない方が常に優れたソリューションです。単にreturn res.end('Unabled to load file xxx');を実行できます

5
3rdEden

私は自分の質問に答えます... :)

手動でエラーをキャッチする方法はないようです。現在、try/catchブロックを含むfunctionを返すヘルパー関数を使用しています。さらに、自分のWebサーバークラスは、リクエスト処理関数がresponse.end()or try/catchヘルパー関数waitfor()(そうでない場合は例外を発生させる)を呼び出すかどうかをチェックします。これにより、リクエストが開発者によって誤って保護されないままになることが大幅に回避されます。 100%エラーが発生しやすいソリューションではありませんが、十分に機能します。

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

このように、安全のためにインラインwaitfor()呼び出しを追加する必要があります。

_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
}
_

実際のチェックメカニズムはもう少し複雑ですが、それがどのように機能するかは明確でなければなりません。誰かが興味を持っているなら、ここに完全なコードを投稿できます。

3
Udo G

とてもいい質問です。私は今、同じ問題に取り組んでいます。おそらく最良の方法は、uncaughtExceptionを使用することです。応答オブジェクトと要求オブジェクトへの参照は問題ではありません。それらを例外オブジェクトにラップして、uncaughtExceptionイベントに渡すことができるためです。このようなもの:

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

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

}

投げて:

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

そして、応答を処理します。

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

このソリューションをまだテストしていませんが、これが機能しなかった理由がわかりません。

3
Marian Galik

1つのアイデア:ヘルパーメソッドを使用してコールバックを作成し、それを使用する標準的な方法にすることができます。これにより、開発者に負担がかかりますが、少なくとも、コールバックを処理する「標準的な」方法を使用して、コールバックを忘れる可能性を低くすることができます。

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

おそらくあなたが探していた答えではないことを知っていますが、ECMAScript(または一般的な関数型プログラミング)の素晴らしい点の1つは、このようなことに対して独自のツールを簡単に展開できることです。

2
jslatts

この記事の執筆時点で、私が見ているアプローチは「約束」を使用することです。

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

これらにより、エラー管理のためにコードとコールバックを適切に構造化でき、さらに読みやすくなります。主に。then()関数を使用します。

someFunction().then(success_callback_func, failed_callback_func);

基本的な例を次に示します。

  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

また、同期マルチスレッドプログラミング(.NET、Java、PHPなど)では、カスタムの未知の例外がキャッチされたときに、意味のある情報をクライアントに返すことはできません。例外に関する情報がない場合は、HTTP 500を返すだけです。

したがって、「秘密」は説明的なエラーオブジェクトを埋めることにあります。これにより、エラーハンドラは意味のあるエラーから正しいHTTPステータス+オプションで説明的な結果にマッピングできます。ただし、process.on( 'uncaughtException')に到着する前に例外をキャッチする必要もあります。

ステップ1:意味のあるエラーオブジェクトを定義する

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;

ステップ2:例外をスローする場合、ハンドラーが例外を意味のないHTTP結果に変換できるようにするプロパティ(ステップ1を参照)を入力します。

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

ステップ3:潜在的に危険なコードを呼び出す場合、エラーをキャッチし、エラーオブジェクト内の追加のコンテキストプロパティを埋めながらそのエラーを再スローします

ステップ4:リクエスト処理中に例外をキャッチする必要があります。これは、非同期エラーをキャッチできる主要なPromiseライブラリ(BlueBirdが素晴らしい)を使用している場合に簡単です。 Promiseを使用できない場合、組み込みのNODEライブラリはコールバックでエラーを返します。

ステップ5:エラーがキャッチされ、何が起こるかを説明する情報が含まれたので、意味のあるHTTP応答にマップするだけで済みます。ここでの良い点は、すべてのエラーを取得し、これらをHTTP応答にマップする、集中化された単一のエラーハンドラーがあることです。

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

関連するベストプラクティスはこちら

0
Yonatan

コードでこの問題を解決するには、2つのことが本当に役立ちました。

  1. 'longjohn'モジュール。完全なスタックトレースを(複数の非同期コールバック全体で)表示できます。
  2. 標準のcallback(err, data)イディオム(CoffeeScriptで表示)内に例外を保持する単純なクロージャーテクニック。

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

これで、安全でないコードをラップでき、コールバックはすべて同じ方法でエラーを処理します:エラー引数をチェックすることによって。

0
Jesse Dailey

最近、WaitForという名前の単純な抽象化を作成して、同期モード(ファイバーに基づく)で非同期関数を呼び出します。 https://github.com/luciotato/waitfor

「堅実」には新しいものです。

wait.forを使用すると、ノードのイベントループをブロックせずに、非同期機能を同期のように使用できます。それはあなたが慣れているものとほとんど同じです:

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

ファイバー内にいるので、ノードのイベントループではなく、「ファイバーをブロックする」ように順番にプログラムできます。

他の例:

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

}

ご覧のとおり、wait.forを使用して、(表示可能な)コールバックなしでノードの非同期関数を同期モードで呼び出すため、すべてのコードを取得できます1つのtry-catchブロック内。

wait.forは、非同期関数のいずれかがerr!== nullを返した場合に例外をスローします

詳細については https://github.com/luciotato/waitfor

0
Lucio M. Tato