Using Error
, try/catch
, throw
to make code easier to debug. They can also make the code structure clearer.
Error Overview
Here are some conclusions:
-
Error
is Standard built-in objects. -
Error
objects are thrown when runtime errors occur. - Usually you create an
Error
object with the intention of raising it using thethrow
keyword. You can handle the error using thetry...catch
construct. - Error types: Besides the generic
Error
constructor, there are still: EvalError, TypeError, etc. in JavaScript. - The
Error
object can also be used as a base object for user-defined exceptions.
A cleaner and more consistent error handling way is that define your own error types deriving from
Error
to be able to throw newMyError()
and useinstanceof MyError
to check the kind of error in the exception handler. This results in cleaner and more consistent error handling code.
Error Instance
Error.prototype.constructor
syntax: new Error(message, options)
JavaScript only tries to read options.cause
if options
is an object.
Note: Error() can be called with or without new. Both create a new Error instance.
const x = Error("I was created using a function call!");
// above has the same functionality as following
const y = new Error('I was constructed via the "new" keyword!');
Error.prototype.name
The name
data property of Error.prototype
is shared by all Error instances. It represents the name for the type of error.
Error.cause
The cause
data property of an Error instance indicates the specific original cause of the error.
It is used when catching and re-throwing an error with a more-specific or useful error message in order to still have access to the original error.
The value of cause can be of any type.
Error: message
The message
data property of an Error instance is a human-readable description of the error.
The message
property combined with the name
property is used by the Error.prototype.toString()
method to create a string representation of the Error.
Error.prototype.stack
This feature is non-standard, but it is de facto implemented by all major JavaScript engines, and the JavaScript standards committee is looking to standardize it.
- The value of
stack
is string. - you can assume it exists and use it for debugging purposes.
- The
stack
property of an Error instance offers a trace of which functions were called, in what order, from which line and file, and with what arguments. - The stack string proceeds from the most recent calls to earlier ones, leading back to the original global scope call.
- Each JavaScript engine uses its own format for stack traces, but they are fairly consistent in their high-level structure.
Example:
function trace() {
throw new Error("trace() failed");
}
function b() {
trace();
}
function a() {
b(3, 4, "\n\n", undefined, {});
}
try {
a("first call, firstarg");
} catch (e) {
document.getElementById("output").textContent = e.stack;
}
then you can see in output
element:
Error: trace() failed
at trace (<anonymous>:2:9)
at b (<anonymous>:5:3)
at a (<anonymous>:8:3)
at <anonymous>:11:3
at init (https://live.mdnplay.dev/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack/runner.html?id=using_the_stack_property:133:23)
at https://live.mdnplay.dev/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack/runner.html?id=using_the_stack_property:146:17
How to Differentiate between similar errors
Sometimes a block of code can fail for reasons that require different handling, but which throw very similar errors (i.e. with the same type and message).
If you don’t have control over the original errors that are thrown, one option is to catch them and throw new Error objects that have more specific messages.
function doWork() {
try {
doFailSomeWay();
} catch (err) {
throw new Error("Failed in some way", { cause: err });
}
try {
doFailAnotherWay();
} catch (err) {
throw new Error("Failed in another way", { cause: err });
}
}
try {
doWork();
} catch (err) {
switch (err.message) {
case "Failed in some way":
handleFailSomeWay(err.cause);
break;
case "Failed in another way":
handleFailAnotherWay(err.cause);
break;
}
}
A good way to extend Error
Read more at stackoverflow.
Best way is using class and extend. But you can still use function.
Use class
Use class
and extend
keywords to subclass Error constructor:
class MyError extends Error {
constructor(message) {
super(message);
this.message = message;
this.name = 'MyError';
}
}
There is no need for this.stack = (new Error()).stack;
trick thanks to super()
call.
For ease of maintaining, use this.name = this.constructor.name;
instead. Try this:
class MyError extends Error {
constructor(message) {
super(message);
this.message = message;
//this.name = 'MyError';
this.name = this.constructor.name;
}
}
Use function
function MyError (message){
this.message = message;
this.name = 'MyError';
Error.captureStackTrace(this);
}
// does the same magic as extends keyword
Object.setPrototypeOf(MyError.prototype, Error.prototype);
Test result
try{
throw new MyError('this is test');
}catch(e){
// true
if(e instanceof Error){
console.log(e)
}
// true
if(e instanceof MyError){
console.log(e)
}
}
throw
The throw
statement throws a user-defined exception. Execution of the current function will stop (the statements after throw won’t be executed), and control will be passed to the first catch
block in the call stack. If no catch
block exists among caller functions, the program will terminate.
Usually, throw
statement throw an Error instance:
function getRectArea(width, height) {
if (isNaN(width) || isNaN(height)) {
throw new Error('Parameter is not a number!');
}
}
try {
getRectArea(3, 'A');
} catch (e) {
console.error(e);
// Expected output: Error: Parameter is not a number!
}
But it can throw any object. and catch
statement can catch that object.
try…catch
The try...catch
statement is comprised of a try
block and either a catch
block, a finally
block, or both. The code in the try block is executed first, and if it throws an exception, the code in the catch
block will be executed. The code in the finally
block will always be executed before control flow exits the entire construct.
A common use case for this is to only catch (and silence) a small subset of expected errors, and then re-throw the error in other cases:
try {
myRoutine();
} catch (e) {
if (e instanceof RangeError) {
// statements to handle this very common expected error
} else {
throw e; // re-throw the error unchanged
}
}
Execute order in Nested try blocks
try {
try {
throw new Error("oops");
} finally {
console.log("finally");
}
} catch (ex) {
console.error("outer", ex.message);
}
// Logs:
// "finally"
// "outer" "oops"
try {
try {
throw new Error("oops");
} catch (ex) {
console.error("inner", ex.message);
} finally {
console.log("finally");
}
} catch (ex) {
console.error("outer", ex.message);
}
// Logs:
// "inner" "oops"
// "finally"
try {
try {
throw new Error("oops");
} catch (ex) {
console.error("inner", ex.message);
throw ex;
} finally {
console.log("finally");
}
} catch (ex) {
console.error("outer", ex.message);
}
// Logs:
// "inner" "oops"
// "finally"
// "outer" "oops"
FAQ
- what is
stack
proerpty.