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.