Error handling mechanism
Error instance object
When JavaScript is parsed or run, once an error occurs, the engine will throw an error object. JavaScript natively provides the Error
constructor, and all errors thrown are instances of this constructor.
var err = new Error("Error");
err.message; // "Something went wrong"
In the above code, we call the Error()
constructor to generate an instance object err
. The Error()
constructor accepts a parameter, which indicates an error message, which can be read from the message
property of the instance. After throwing the Error
instance object, the entire program will be interrupted at the place where the error occurred and will not be executed anymore.
The JavaScript language standard only mentions that the Error
instance object must have the message
attribute, which indicates the prompt message when an error occurs, and no other attributes are mentioned. Most JavaScript engines also provide name
and stack
attributes for Error
instances, which represent the name of the error and the stack of the error, respectively, but they are non-standard and not available in every implementation.
-message: error message -name: Error name (non-standard attribute) -stack: Wrong stack (non-standard attribute)
Using the two attributes name
and message
, you can get a rough idea of what happened.
if (error.name) {
console.log(error.name + ": " + error.message);
}
The stack
attribute is used to view the stack when the error occurred.
function throwit() {
throw new Error("");
}
function catchit() {
try {
throwit();
} catch (e) {
console.log(e.stack); // print stack trace
}
}
catchit();
// Error
// at throwit (~/examples/throwcatch.js:9:11)
// at catchit (~/examples/throwcatch.js:3:9)
// at repl:1:5
In the above code, the innermost layer of the error stack is the throwit
function, then the catchit
function, and finally the function's operating environment.
Native error type
The Error
instance object is the most general type of error. Based on it, JavaScript also defines six other error objects. In other words, there are 6 derived objects of Error
.
SyntaxError object
The SyntaxError
object is a syntax error that occurred when parsing the code.
// variable name error
var 1a;
// Uncaught SyntaxError: Invalid or unexpected token
// missing parentheses
console.log'hello');
// Uncaught SyntaxError: Unexpected string
The errors in the above code can be found in the syntax analysis stage, so SyntaxError
will be thrown. The first error message is "token is illegal", and the second error message is "the string does not meet the requirements".
ReferenceError object
The ReferenceError
object is an error that occurs when referencing a variable that does not exist.
// use a non-existent variable
unknownVariable;
// Uncaught ReferenceError: unknownVariable is not defined
Another trigger scenario is to assign a value to an object that cannot be assigned, such as assigning a value to the result of a function.
// The left side of the equal sign is not a variable
console.log() = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment
The above code assigns a value to the running result of the function console.log
, and the result raises a ReferenceError
error.
RangeError object
The RangeError
object is an error that occurs when a value exceeds the valid range. There are mainly several situations, one is that the length of the array is negative, the other is that the method parameters of the Number
object are out of range, and the function stack exceeds the maximum value.
// The length of the array must not be negative
new Array(-1);
// Uncaught RangeError: Invalid array length
TypeError Object
The TypeError
object is an error that occurs when a variable or parameter is not of the expected type. For example, if you use the new
command on primitive values such as strings, booleans, numbers, etc., this kind of error will be thrown, because the parameter of the new
command should be a constructor.
new 123();
// Uncaught TypeError: 123 is not a constructor
var obj = {};
obj.unknownMethod();
// Uncaught TypeError: obj.unknownMethod is not a function
In the second case of the above code, calling a method whose object does not exist will also throw a TypeError
error, because the value of obj.unknownMethod
is undefined
, not a function.
URIError object
The URIError
object is an error thrown when the parameters of URI-related functions are incorrect, mainly involving encodeURI()
, decodeURI()
, encodeURIComponent()
, decodeURIComponent()
, escape()
and unescape()
these six functions.
decodeURI("%2");
// URIError: URI malformed
EvalError Object
When the eval
function is not executed correctly, an EvalError
error will be thrown. This error type is no longer used, it is only kept in order to ensure compatibility with the previous code.
to sum up
The above 6 kinds of derived errors, together with the original Error
object, are all constructors. Developers can use them to manually generate instances of error objects. These constructors all accept a parameter, which represents an error message (message).
var err1 = new Error("An error has occurred!");
var err2 = new RangeError(
"An error occurred, the variable is out of the valid range!"
);
var err3 = new TypeError("An error occurred, the variable type is invalid!");
err1.message; // "Something went wrong!"
err2.message; // "An error occurred, the variable is out of the valid range!"
err3.message; // "An error occurred, the variable type is invalid!"
Custom error
In addition to the seven error objects natively provided by JavaScript, you can also define your own error objects.
function UserError(message) {
this.message = message || "Default message";
this.name = "UserError";
}
UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
The above code customizes an error object UserError
and let it inherit the Error
object. Then, you can generate this custom type of error.
new UserError("This is a custom error!");
throw statement
The function of the throw
statement is to manually interrupt program execution and throw an error.
var x = -1;
if (x <= 0) {
throw new Error("x must be a positive number");
}
// Uncaught Error: x must be a positive number
In the above code, if the variable x
is less than or equal to 0
, an error is manually thrown to tell the user that the value of x
is incorrect, and the entire program will be interrupted here. As you can see, the error thrown by throw
is its parameter, here is an instance of Error
object.
throw
can also throw custom errors.
function UserError(message) {
this.message = message || "Default message";
this.name = "UserError";
}
throw new UserError("Something went wrong!");
// Uncaught UserError {message: "Something went wrong!", name: "UserError"}
In the above code, what throw
throws is an instance of UserError
.
In fact, throw
can throw any type of value. In other words, its parameters can be any value.
// throw a string
throw "Error! ";
// Uncaught Error!
// throw a value
throw 42;
// Uncaught 42
// throw a boolean
throw true;
// Uncaught true
// throw an object
throw {
toString: function () {
return "Error!";
},
};
// Uncaught {toString: ƒ}
For the JavaScript engine, the program is aborted when it encounters the throw
statement. The engine will receive the information thrown by throw
, which may be an error instance or other types of values.
try...catch structure
Once an error occurs, the program is aborted. JavaScript provides a try...catch
structure, which allows you to handle errors and choose whether to execute them.
try {
throw new Error("Something went wrong!");
} catch (e) {
console.log(e.name + ": " + e.message);
console.log(e.stack);
}
// Error: Something went wrong!
// at <anonymous>:3:9
// ...
In the above code, the try
code block throws an error (the above example uses the throw
statement), and the JavaScript engine immediately transfers the execution of the code to the catch
code block, or the error is caused by the catch
code block Caught. catch
accepts a parameter that represents the value thrown by the try
code block.
If you are not sure whether certain codes will report errors, you can put them in the try...catch
code block for further error handling.
try {
f();
} catch (e) {
// handle errors
}
In the above code, if the function f
executes and reports an error, the catch
code block will be executed, and then the error will be handled.
After the catch
code block catches the error, the program will not be interrupted and will continue to execute according to the normal flow.
try {
throw "Something went wrong";
} catch (e) {
console.log(111);
}
console.log(222);
// 111
// 222
In the above code, the error thrown by the try
code block is caught by the catch
code block, and the program will continue to execute downward.
In the catch
code block, you can also throw errors, and even use the nested try...catch
structure.
var n = 100;
try {
throw n;
} catch (e) {
if (e <= 50) {
// ...
} else {
throw e;
}
}
// Uncaught 100
In the above code, another error was thrown in the catch
code.
In order to catch different types of errors, judgment statements can be added to the catch
code block.
try {
foo.bar();
} catch (e) {
if (e instanceof EvalError) {
console.log(e.name + ": " + e.message);
} else if (e instanceof RangeError) {
console.log(e.name + ": " + e.message);
}
// ...
}
In the above code, after catching the error, catch
will determine the error type (EvalError
or RangeError
) and perform different processing.
finally code block
The try...catch
structure allows a finally
code block to be added at the end, which represents a statement that must be run at the end regardless of whether there is an error.
function cleansUp() {
try {
throw new Error("Something went wrong......");
console.log("This line will not be executed");
} finally {
console.log("Complete the cleanup work");
}
}
cleansUp();
// Complete the cleanup
// Uncaught Error: Something went wrong...
// at cleansUp (<anonymous>:3:11)
// at <anonymous>:10:1
In the above code, since there is no catch
statement block, once an error occurs, the code will interrupt its execution. Before interrupting execution, the finally
code block will be executed first, and then the user will be prompted with an error message.
function idle(x) {
try {
console.log(x);
return "result";
} finally {
console.log("FINALLY");
}
}
idle("hello");
// hello
// FINALLY
In the above code, there is no error in the try
code block, and it also contains a return
statement, but the finally
code block will still be executed. Moreover, the return value of this function is still result
.
The following example shows that the execution of the return
statement is arranged before the finally
code, and it returns only after the finally
code is executed.
var count = 0;
function countUp() {
try {
return count;
} finally {
count++;
}
}
countUp();
// 0
count;
// 1
The above code shows that the value of count
in the return
statement is obtained before the finally
code block runs.
Below is a typical scenario of the usage of the finally
code block.
openFile();
try {
writeFile(Data);
} catch (e) {
handleError(e);
} finally {
closeFile();
}
The above code first opens a file, and then writes the file in the try
code block. If no error occurs, run the finally
code block to close the file; once an error occurs, first use the catch
code block to handle the error, and then Use the finally
code block to close the file.
The following example fully reflects the order of execution among the three of try...catch...finally
.
function f() {
try {
console.log(0);
throw "bug";
} catch (e) {
console.log(1);
return true; // This sentence was originally delayed until the end of the finally code block before execution
console.log(2); // will not run
} finally {
console.log(3);
return false; // This sentence will overwrite the previous sentence return
console.log(4); // will not run
}
console.log(5); // will not run
}
var result = f();
// 0
// 1
// 3
result;
// false
In the above code, the finally
code block will be executed first before the catch
code block finishes its execution.
In the catch
code block, the flag that triggers the transfer to the finally
code block includes not only the return
statement but also the throw
statement.
function f() {
try {
throw "Something went wrong! ";
} catch (e) {
console.log("Internal error caught");
throw e; // This sentence would have to wait until finally is over before executing
} finally {
return false; // return directly
}
}
try {
f();
} catch (e) {
// will not be executed here
console.log('caught outer "bogus"');
}
// Capture internal error
In the above code, after entering the catch
code block, as soon as it encounters the throw
statement, it will execute the finally
code block, which contains the return false
statement, so it returns directly, and will not go back to execute catch
The rest of the code block.
Inside the try
code block, you can also use the try
code block.
try {
try {
consle.log("Hello world!"); // error
} finally {
console.log("Finally");
}
console.log("Will I run?");
} catch (error) {
console.error(error.message);
}
// Finally
// consle is not defined
In the above code, there is also a try
in try
. The inner try
reports an error (console
is misspelled), then the inner finally
code block is executed, and then an error is thrown, which is caught by the outer catch
.
Reference link
- Jani Hartikainen, JavaScript Errors and How to Fix Them