How can I rethrow an error or exception in nodejs/javascript and include a custom message.
I have the following code
var json = JSON.parse(result);
and I wanted to include the result
content in the exception message should any parsing error occurs. Something like this.
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. }
The problem here is that I lose my stack trace.
Is there a way to do it similar to java
?
throw new Error("JSON response: " + result, ex);
Therefore, you should log a stacktrace if, and only if, and always if, the exception indicates a bug in the program. However, that does not always indicate that a method you write should catch and log the exception.
throw new Error('something went wrong') — will create an instance of an Error in JavaScript and stop the execution of your script, unless you do something with the Error.
I'm not aware of a native method like Java's and I've not found an elegant solution for wrapping errors yet.
The problem with creating a new Error
is you can lose metadata that was attached to the original Error
that was thrown, the stack trace and type are generally the important items lost.
Making modifications to an existing thrown error is quicker, but it is still possible to modify data from the error out of existence. It also feels wrong to be poking around in an error that was created somewhere else.
The .stack
property of a new Error
is a plain string and can be modified to say what you like before it is thrown. Replacing an errors stack
property completely can get really confusing for debugging though.
When the original thrown error and the error handler are in separate locations or files (which is common with promises), you might be able to trace the source of the original error but not trace the handler where the error was actually trapped. To avoid this it's good to keep some references to both the original and new error in the stack
. It's also useful to have access to the complete original error if there was additional metadata stored in it.
Here's an example of catching an error, wrapping it in a new error but adding the original stack
and storing the error
:
try { throw new Error('First one') } catch (error) { let e = new Error(`Rethrowing the "${error.message}" error`) e.original_error = error e.stack = e.stack.split('\n').slice(0,2).join('\n') + '\n' + error.stack throw e }
Which throws:
/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)
So we've created a new generic Error
. Unfortunately the type of the original error becomes hidden from the output but the error
has been attached as .original_error
so it can still be accessed. The new stack
has been largely removed except for the generating line which is important, and the original errors stack
appended.
Any tools that try to parse stack traces might not work with this change or best case, they detect two errors.
Making this into a reusable ES2015+ Error class:
class RethrownError extends Error { constructor(message, error){ super(message) this.name = this.constructor.name if (!error) throw new Error('RethrownError requires a message and error') this.original_error = error this.stack_before_rethrow = this.stack const 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)
Results in
/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)
Then you know that whenever you see a RethrownError
that the original error will still be available at .original_error
.
This method is not perfect but it means I can re-type known errors from underlying modules into generic types that be handled more easily, usually with bluebirds filtered catch .catch(TypeError, handler)
Note stack
becomes enumerable here
Some times you will need to keep the original error mostly as is.
In this case you can just append/insert the new info onto existing stack.
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 }
Which returns
/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)
Also note that the stack processing is simple and assumes the error message is a single line. If you run into multi line error messages you might need to look for \n at
to terminate the message.
If all you want to do is change the message, you can just change the message:
try { throw new Error("Original Error"); } catch(err) { err.message = "Here is some context -- " + err.message; throw err; }
UPDATE:
If the message property is readonly, you can create a new object using the original error as the prototype and assigning a new message:
try { // line 12 document.querySelectorAll("div:foo"); // Throws a DOMException (invalid selector) } catch(err) { let message = "Here is some context -- " + err.message; let e = Object.create( err, { message: { value: message } } ); throw e; // line 17 }
Unfortunately, the logged message for an Exception is just "Uncaught exception" without the message that came with the Exception, so it might be helpful to create an Error and give it the same stack so the logged message will include the error message:
try { // line 12 document.querySelectorAll("div:foo"); // Throws a DOMException (invalid selector) } catch(err) { e = new Error( "Here is some context -- " + err.message ); e.stack = err.stack; throw e; // line 17 }
Since the snippet output shows the line number of the re-throw, this confirms that the stack is preserved:
try { // line 12 try { // line 13 document.querySelectorAll("div:foo"); // Throws a DOMException (invalid selector) } catch(err) { console.log( "Stack starts with message: ", err.stack.split("\n")[0] ); console.log( "Inner catch from:", err.stack.split("\n")[1] ); e = new Error( "Here is some context -- " + err.message ); // line 18 console.log( "New error from:", e.stack.split("\n")[1] ); e.stack = err.stack; throw e; // line 21 } } catch(err) { console.log( "Outer catch from:", err.stack.split("\n")[1] ); throw err; // line 25 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With