web-dev-qa-db-ja.com

NodeJSで例外を再スローし、スタックトレースを失わない

Nodejs/javascriptでエラーまたは例外を再スローし、カスタムメッセージを含めるにはどうすればよいですか。

次のコードがあります

var json = JSON.parse(result);

また、解析エラーが発生した場合は、例外メッセージにresultの内容を含めたかったのです。このようなもの。

1.  try {
2.    var json = JSON.parse(result);
3.    expect(json.messages.length).to.be(1);
4.  } catch(ex) {
5.    throw new Error(ex.message + ". " + "JSON response: " + result);
6.  }

ここでの問題は、スタックトレースが失われることです。

Javaのようにする方法はありますか?

throw new Error("JSON response: " + result, ex);
39
alayor

私はJavaのようなネイティブメソッドを認識していませんし、エラーをラップするためのエレガントなソリューションをまだ見つけていません。

_new Error_の作成に関する問題は、スローされた元のErrorに添付されていたメタデータを失う可能性があることです。スタックトレースとタイプは、一般に失われる重要なアイテムです。

既存のスローされたエラーを変更する方が迅速ですが、存在しないエラーからデータを変更することは可能です。また、他の場所で作成されたエラーをぶちまけるのは間違っていると感じています。

新しいエラーと新しいスタックを作成する

新しいErrorの_.stack_プロパティはプレーンな文字列であり、スローされる前に変更して好きなように言うことができます。エラーstackプロパティを完全に置き換えると、デバッグが非常に混乱する可能性があります。

スローされた元のエラーとエラーハンドラーが別々の場所またはファイルにある場合(これは、promiseと共通です)、元のエラーのソースをトレースできますが、エラーが実際にトラップされたハンドラーはトレースできません。これを回避するには、元のエラーと新しいエラーの両方への参照をstackに保持することをお勧めします。追加のメタデータが格納されている場合、元の完全なエラーにアクセスできると便利です。

エラーをキャッチし、新しいエラーにラップするが、元のstackを追加してerrorを格納する例を次に示します。

_try {
  throw new Error('First one')
} catch (error) {
  let e = new Error(`Rethrowing the "${error.message}" error`)
  e.original = error
  e.stack = e.stack.split('\n').slice(0,2).join('\n') + '\n' +
            error.stack
  throw e
}
_

投げる:

_/so/42754270/test.js:9
    throw e
    ^

Error: Rethrowing the "First one" error
    at test (/so/42754270/test.js:5:13)
Error: First one
    at test (/so/42754270/test.js:3:11)
    at Object.<anonymous> (/so/42754270/test.js:13:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
_

そこで、新しいジェネリックErrorを作成しました。残念ながら、元のエラーのタイプは出力から隠されますが、errorは_.original_として添付されているため、引き続きアクセスできます。新しいstackは、重要な生成行と追加された元のエラーstackを除いて、大幅に削除されました。

スタックトレースを解析しようとするツールは、この変更またはベストケースで機能しない可能性があり、2つのエラーを検出します。

ES2015エラークラス

これを再利用可能なES2015 +エラークラスにしています...

_// Standard error extender from @deployable/errors

class ExtendedError extends Error {
  constructor(message){
    super(message)
    this.name = this.constructor.name
    this.message = message
    if (typeof Error.captureStackTrace === 'function'){
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

class RethrownError extends ExtendedError {
  constructor(message, error){
    super(message)
    if (!error) throw new Error('RethrownError requires a message and error')
    this.original = error
    this.new_stack = this.stack
    let message_lines =  (this.message.match(/\n/g)||[]).length + 1
    this.stack = this.stack.split('\n').slice(0, message_lines+1).join('\n') + '\n' +
                 error.stack
  }
}

throw new RethrownError(`Oh no a "${error.message}" error`, error)
_

結果

_/so/42754270/test2.js:31
    throw new RethrownError(`Oh no a "${error.message}"" error`, error)
    ^

RethrownError: Oh no a "First one" error
    at test (/so/42754270/test2.js:31:11)
Error: First one
    at test (/so/42754270/test2.js:29:11)
    at Object.<anonymous> (/so/42754270/test2.js:35:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
_

次に、RethrownErrorが表示されたときはいつでも、元のエラーは_.original_で引き続き利用できることがわかります。

この方法は完璧ではありませんが、基盤となるモジュールの既知のエラーを、通常はブルーバードでより簡単に処理できるジェネリック型に再入力できることを意味します filtered catch .catch(TypeError, handler)

変更されたスタックでの同じエラー

元のエラーをほとんどそのままにしておく必要がある場合があります。

この場合、新しい情報を既存のスタックに追加/挿入するだけです。

_file = '/home/jim/plumbers'
try {
   JSON.parse('k')
} catch (e) {
   let message = `JSON parse error in ${file}`
   let stack = new Error(message).stack
   e.stack = e.stack + '\nFrom previous ' + stack.split('\n').slice(0,2).join('\n') + '\n'
   throw e
}
_

どっちが帰る

_/so/42754270/throw_error_replace_stack.js:13
       throw e
       ^

SyntaxError: Unexpected token k in JSON at position 0
    at Object.parse (native)
    at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:8:13)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:394:7)
    at startup (bootstrap_node.js:149:9)
From previous Error: JSON parse error in "/home/jim/plumbers"
    at Object.<anonymous> (/so/42754270/throw_error_replace_stack.js:11:20)
_

また、スタック処理は単純で、エラーメッセージは1行であると想定しています。複数行のエラーメッセージが表示された場合は、_\n at_を探してメッセージを終了する必要がある場合があります。

47
Matt

メッセージを変更するだけの場合は、メッセージを変更するだけです。

try {
  throw new Error("Original Error");
} catch(err) {
  err.message = "Here is some context -- " + err.message;
  throw err;
}
11
ShadSterling

また、tryチェーンにエラーをスローし続けることもできます。何かを変更したい場合は、途中で変更します:bのthrowステートメントの前。

function a() {
    throw new Error('my message');
}

function b() {
    try {
        a();
    } catch (e) {
        // add / modify properties here
        throw e;
    }
}

function c() {
    try {
        b();
    } catch (e) {
        console.log(e);
        document.getElementById('logger').innerHTML = e.stack;
    }
}
c();
<pre id="logger"></pre>
2

エラーをラップする簡単な方法を提供するJoyentの verror module を確認することをお勧めします。

var originError = new Error('No such file or directory');
var err = new VError(originError, 'Failed to load configuration');
console.error(err.message);

これは印刷されます:

Failed to load configuration: No such file or directory
0
Noki