Original book: http://eloquentjavascript.net/08_error.html
1. Strict Mode
"use strict";
1.function, in the first line;
2.js file, in the first line;
3. <script> tag, in the first line;
4. It's no use if you don't put it in the first line.
Undefined variables
function canYouSpotTheProblem() { "use strict"; for (counter = 0; counter < 10; counter++) console.log("Happy happy"); } canYouSpotTheProblem(); // → ReferenceError: counter is not defined
If, under normal circumstances, an undefined variable is used, the system automatically defines a global variable. In strict mode, global variables are not defined and errors are reported directly.
this
What happens if a function is a method of object and there is no corresponding object when it is called?
In normal mode, this points to global variables. In string mode, this is equal to undefined.
function Person(name) { this.name = name; } var ferdinand = Person("Ferdinand"); // oops console.log(name); // → Ferdinand
In this code, when you call the Person constructor, you forget to write new. So instead of creating a new Person object, it just executes Person(name) as a normal function. At this point, this is a global variable. In other words, this.name = name; creates an attribute name for the global variable variable variable and assigns a value. Look closely at console.log(name); where does the name variable come from?
"use strict"; function Person(name) { this.name = name; } // Oops, forgot 'new' var ferdinand = Person("Ferdinand"); // → TypeError: Cannot set property 'name' of undefined
In this code, this is undefined, adding the name attribute to an undefined, and of course, reporting an error.
The Vector class in the previous chapter is an example
function Vector(x, y) { this.x = x; this.y = y; } Vector.prototype.plus = function(other) { return new Vector(this.x + other.x, this.y + other.y); };
Write a set of test cases
function testVector() { var p1 = new Vector(10, 20); var p2 = new Vector(-10, 5); var p3 = p1.plus(p2); if (p1.x !== 10) return "fail: x property"; if (p1.y !== 20) return "fail: y property"; if (p2.x !== -10) return "fail: negative x property"; if (p3.x !== 0) return "fail: x from plus"; if (p3.y !== 25) return "fail: y from plus"; return "everything ok"; } console.log(testVector()); // → everything ok
There are already a number of ready-made Test Suite available, similar to the above example. It will simplify the writing of test code and provide better output format.
1. Debugging
Look at an example and find out the problem.
Given Radix (decimal, hexadecimal, etc.), print an integer according to this radix.
function numberToString(n, base) { var result = "", sign = ""; if (n < 0) { sign = "-"; n = -n; } do { result = String(n % base) + result; n /= base; } while (n > 0); return sign + result; } console.log(numberToString(13, 10)); // → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
Debugging with console.log
Inside the do.. While loop, add console.log(n);
As you can see, after n/= base, it's not an integer. So, this line should be written: n = Math.floor(n/base);
With the debugging function of browser, add breakpoints and single-step tracking
This is chrome.

Or, add a line of debugger to the code; if the browser's Deevelop function is turned on, it automatically becomes a breakpoint

1. Error Propagation
Not all code and data are controllable, such as user input, use of third-party code base, access to data from the network, etc.
Look at an example.
This function returns the user input number:
function promptNumber(question) { var result = Number(prompt(question, "")); if (isNaN(result)) return null; else return result; } console.log(promptNumber("How many trees do you see?"));
Unfortunately, users can enter any character, and we can't get it right every time. So, error handling is done here, and null is returned when qualified numbers are not available.
Returning a special value in a function to represent an error/exception is a common method.
But sometimes, this method is not very practical, because:
1. Sometimes no suitable "special value" can be found. Maybe null is also a valid return value?
2. When calling this function, it is necessary to judge whether the return value is legitimate or not. It's also a troublesome thing. In particular, when the level of function calls is deep, some defensive code should be written at each level.
1. Exceptions
Looking back at the function stack in Chapter 3, every function call will press a layer of stack, and every return, a layer of stack will pop up.
exception is similar to a super return, which can pop up a multi-tier stack at a time until it reaches the level of catch.
Basic Usage
function promptDirection(question) { var result = prompt(question, ""); if (result.toLowerCase() == "left") return "L"; if (result.toLowerCase() == "right") return "R"; throw new Error("Invalid direction: " + result); } function look() { if (promptDirection("Which way?") == "L") return "a house"; else return "two angry bears"; } try { console.log("You see", look()); } catch (error) { console.log("Something went wrong: " + error); }
1. throw
Throw an exception
2. Error
The predefined classes have two main attributes:
message: Error Description
Stack: Current stack3. try
Blocks of code that may throw exceptions
4. catch
After the anomaly, jump here. Then the code will execute normally.
1. Cleaning Up After Exceptions
There is such a case.
var context = null; function withContext(newContext, body) { var oldContext = context; context = newContext; var result = body(); context = oldContext; return result; }
Note that before executing body(), we save the context, and after executing body(), we restore the original context. What if exception s are thrown in body()? The last two lines of code won't execute.
finally
function withContext(newContext, body) { var oldContext = context; context = newContext; try { return body(); } finally { context = oldContext; } }
finnally blocks are executed regardless of whether an exception is thrown or not. Note that even after the return statement, it executes.
Look at a complete example:
var context = null; function withContext(newContext, body) { var oldContext = context; context = newContext; try { return body(); } finally { context = oldContext; } } try { withContext(5, function () { if (context < 10) throw new Error("Not enough context!"); }); } catch(e) { console.log("Ignoring: " + e); } console.log(context);
1. Selective Catching
If an exception is not caught, it will pop up to the bottom of the stack. At this point, the system (browser) will catch it and typically print out the error description in the console.
Sometimes, we want to capture some kinds of exception and let others be processed by the system. If you follow the previous example, you will catch all the exceptions thrown inside body(), which may not be what we want.
Look at an example:
for (;;) { try { var dir = promtDirection("Where?"); // ← typo! console.log("You chose ", dir); break; } catch (e) { console.log("Not a valid direction. Try again."); } }
The original idea of this code is to break when the legitimate user input is obtained and jump out of the for loop. However, the promtDirection function name is misspelled. This exception should have been handled by the system. The catch below captures it forcibly, resulting in a dead loop.
Error is a message attribute, so maybe we can distinguish different types of exception by different messages. But this is not good, because message is for people to see, can not be rigorously defined a type, arbitrariness is strong (translated into different languages, modified, etc.).
Define different types of Error
Like system predefined: Error, SyntaxError, Reference Error, etc.
Define an InputError, inherit Error.prototype, and, through Error's construction, get the current stack.function InputError(message) { this.message = message; this.stack = (new Error()).stack; } InputError.prototype = Object.create(Error.prototype); InputError.prototype.name = "InputError";
The Error type provided by the system has a name attribute. We also add it.
function promptDirection(question) { var result = prompt(question, ""); if (result.toLowerCase() == "left") return "L"; if (result.toLowerCase() == "right") return "R"; throw new InputError("Invalid direction: " + result); } for (;;) { try { var dir = promptDirection("Where?"); console.log("You chose ", dir); break; } catch (e) { if (e instanceof InputError) console.log("Not a valid direction. Try again."); else throw e; } }
Look closely at the catch code block:
1. Identify different Error types with instanceof;
2. Continuous throw of other types.
js itself does not provide assert, you can define a
It's very simple.function AssertionFailed(message) { this.message = message; } AssertionFailed.prototype = Object.create(Error.prototype); function assert(test, message) { if (!test ) { throw new AssertionFailed(message); } }
Give it a try:
function lastElement(array) { assert(array.length > 0, "empty array in lastElement"); return array[array.length -1]; }
The two exercises in this chapter are very simple. Just practice customizing Error types and using finnally.