web-dev-qa-db-fra.com

Re-lever l'exception dans NodeJS et ne pas perdre la trace de la pile

Comment puis-je renvoyer une erreur ou une exception dans nodejs/javascript et inclure un message personnalisé.

J'ai le code suivant

var json = JSON.parse(result);

et je voulais inclure le contenu result dans le message d'exception en cas d'erreur d'analyse syntaxique. Quelque chose comme ça.

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

Le problème ici est que je perds ma trace de pile.

Existe-t-il un moyen de le faire similaire à Java?

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

Je ne connais pas de méthode native comme Java et je n'ai pas encore trouvé de solution élégante pour envelopper les erreurs.

Le problème avec la création d'un new Error Est que vous pouvez perdre des métadonnées attachées au Error d'origine qui a été jeté, la trace et le type de pile sont généralement les éléments importants perdus.

Il est plus rapide d'apporter des modifications à une erreur levée existante, mais il est toujours possible de modifier les données de l'erreur hors de son existence. Il semble également mal de fouiller dans une erreur qui a été créée ailleurs.

Créer une nouvelle erreur et une nouvelle pile

La propriété .stack D'un nouveau Error est une chaîne simple et peut être modifiée pour dire ce que vous aimez avant qu'elle ne soit lancée. Le remplacement complet d'une propriété stack d'erreurs peut cependant être très déroutant pour le débogage.

Lorsque l'erreur levée d'origine et le gestionnaire d'erreurs se trouvent dans des emplacements ou des fichiers distincts (ce qui est courant avec les promesses), vous pouvez peut-être tracer la source de l'erreur d'origine mais pas tracer le gestionnaire où l'erreur a été réellement interceptée. Pour éviter cela, il est bon de conserver quelques références à la fois à l'erreur d'origine et à la nouvelle dans le stack. Il est également utile d'avoir accès à l'erreur d'origine complète si des métadonnées supplémentaires y étaient stockées.

Voici un exemple de capture d'une erreur, de l'encapsuler dans une nouvelle erreur mais en ajoutant le stack d'origine et en stockant le 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
}

Ce qui jette:

/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)

Nous avons donc créé un nouveau générique Error. Malheureusement, le type de l'erreur d'origine est masqué dans la sortie, mais le error a été attaché en tant que .original Afin qu'il soit toujours accessible. Le nouveau stack a été en grande partie supprimé, à l'exception de la ligne de génération qui est importante, et les erreurs d'origine stack ont été ajoutées.

Tous les outils qui tentent d'analyser les traces de pile peuvent ne pas fonctionner avec cette modification ou, dans le meilleur des cas, ils détectent deux erreurs.

Classes d'erreur ES2015

Transformation en une classe d'erreur ES2015 + réutilisable ...

// 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)

Résulte en

/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)

Vous savez alors qu'à chaque fois que vous voyez un RethrownError que l'erreur d'origine sera toujours disponible dans .original.

Cette méthode n'est pas parfaite, mais cela signifie que je peux retaper les erreurs connues des modules sous-jacents en types génériques qui peuvent être traités plus facilement, généralement avec des oiseaux bleus catch filtré .catch(TypeError, handler)

Même erreur avec une pile modifiée

Parfois, vous devrez conserver l'erreur d'origine en grande partie telle quelle.

Dans ce cas, vous pouvez simplement ajouter/insérer les nouvelles informations sur la pile existante.

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
}

Qui revient

/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)

Notez également que le traitement de la pile est simple et suppose que le message d'erreur est une seule ligne. Si vous rencontrez des messages d'erreur sur plusieurs lignes, vous devrez peut-être rechercher \n at Pour terminer le message.

47
Matt

Si tout ce que vous voulez faire est de changer le message, vous pouvez simplement changer le message:

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

vous pouvez également continuer à lancer l'erreur dans votre chaîne d'essai. Si vous souhaitez modifier quoi que ce soit, faites-le en cours de route: avant l'instruction throw en b.

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
Michael McLaughlin

Vous voudrez peut-être jeter un œil au module verror de Joyent, qui fournit un moyen facile de récapituler les erreurs:

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

Cela imprimera:

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