You do not know the JS error and call stack common sense

Most engineers probably did not pay attention to before JS objects in error, the error stack of details, even if their daily routine will face a lot of error, some students even look ignorant in front of the console to force mistakes, do not know where to start troubleshooting If you have a system of understanding the content of this article to explain, it will calm a lot. The error stack clean-up allows you to effectively remove noise information, focus on the really important place. In addition, if you understand the various attributes of Error in the end what is, you can make better use of him.

Next, we went straight to the theme.

Call stack mechanism

Before discussing JS errors, we must understand the working mechanism of the call stack (Call Stack), in fact, this mechanism is very simple, if you have a clear picture of this, you can skip this part.

Simply put: when the function is called, will be added to the top of the call stack after the execution, the function will be removed from the top of the call stack , the key to this data structure is that the LIFO, better known as the LIFO . For example, when we call a function of x inside the function y, the call stack from the bottom up in order that y -> x.

We give another code examples:

function c() {
    console.log('c');
}

function b() {
    console.log('b');
    c();
}

function a() {
    console.log('a');
    b();
}

a();

This code runs, the first a will be added to the top of the call stack, and then, because the internal b a call, followed by b is added to the top of the call stack, when internal calls b c also similar. When calling c, we call stack from the bottom up would be in this order: a -> b -> c. , C c is removed after completion of execution from the call stack, control flows back to b, the call stack becomes: a -> b, and b after executing the call stack becomes: a, when performing a finished, it will be removed from the call stack.

In order to better illustrate the working mechanism of the call stack, we have a little change to the code above, use console.trace to the current call stack output to the console, you can think of each line console.trace print out the call stack appears the reason is that it is following the line calls caused.

function c() {
    console.log('c');
    console.trace();
}

function b() {
    console.log('b');
    c();
}

function a() {
    console.log('a');
    b();
}

a();

When we  Node.js of REPL  run this code, you will get the following results:

Trace
    at c (repl:3:9)
    at b (repl:3:1)
    at a (repl:3:1)
    at repl:1:1 // <-- 从这行往下的内容可以忽略,因为这些都是 Node 内部的东西
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)

Obviously, when we internally call console.trace c when the call stack from the bottom-up structure is: a -> b -> c. If the code again minor changes, called after executing the b, c, as follows:

function c() {
    console.log('c');
}

function b() {
    console.log('b');
    c();
    console.trace();
}

function a() {
    console.log('a');
    b();
}

a();

You can see by the output, then print the call stack from the bottom up are: a -> b, c have not, because after executing the c it removed from the call stack.

Trace
    at b (repl:4:9)
    at a (repl:3:1)
    at repl:1:1  // <-- 从这行往下的内容可以忽略,因为这些都是 Node 内部的东西
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.onLine (repl.js:513:10)

Call stack at summarize again working mechanism: the calling function when will be pushed to the top of the call stack, and after the execution is completed, it will be removed from the call stack.

Error object and error handling

When an error occurs in the code, we would normally throw an Error object. Error objects can be used as a prototype to expand and create custom error type. prototype Error object has the following properties:

  • constructor - prototyping function is responsible for this example;
  • message - the error message;
  • name - the wrong name;

The above properties are standard, and some JS runtime environment also provides attributes other than standard properties, such as Node.js, Firefox, Chrome, Edge, IE 10, Opera and Safari 6+ there will stack attribute that contains the error code call stack, then we referred to the error stack. Error stack contains a complete call stack information when generating the error . If you want to learn more about the properties of non-standard Error object, I strongly recommend you read the MDN of this article .

When an error is thrown, you must use the throw keyword. To trap the error thrown, must be used to try catch statement may wrap error code blocks, the catch may be received when a parameter which is a parameter error is thrown. Similar to the Java, JS may have finally after the try catch statement, regardless of whether the code is thrown in front of the code will be executed inside the error finally, common use of the language are: do some clean-up work finally in.

In addition, you can use the try statement did not catch, but must be followed to keep up finally, which means we can use three different forms of try statements:

  • try ... catch
  • try ... finally
  • try ... catch ... finally

try statement can also be nested try statement, such as:

try {
    try {
        throw new Error('Nested error.'); // 这里的错误会被自己紧接着的 catch 捕获
    } catch (nestedErr) {
        console.log('Nested catch'); // 这里会运行
    }
} catch (err) {
    console.log('This will not run.');  // 这里不会运行
}

try statement can also be nested in the catch, and finally statements, such as the following two examples:

try {
    throw new Error('First error');
} catch (err) {
    console.log('First catch running');
    try {
        throw new Error('Second error');
    } catch (nestedErr) {
        console.log('Second catch running.');
    }
}
try {
    console.log('The try block is running...');
} finally {
    try {
        throw new Error('Error inside finally.');
    } catch (err) {
        console.log('Caught an error inside the finally block.');
    }
}

Also note that you can not throw any value Error object . This may look cool, but the project is strongly recommended practice. If you happen to need to deal with the wrong call stack information and other meaningful metadata, non-throwing an error Error object will make you very embarrassing situation.

If we have the following code:

function runWithoutThrowing(func) {
    try {
        func();
    } catch (e) {
        console.log('There was an error, but I will not throw it.');
        console.log('The error\'s message was: ' + e.message)
    }
}

function funcThatThrowsError() {
    throw new TypeError('I am a TypeError.');
}

runWithoutThrowing(funcThatThrowsError);

If runWithoutThrowing incoming caller function can throw an Error object, this code will not have any problem if they threw out the string would be a problem, such as:

function runWithoutThrowing(func) {
    try {
        func();
    } catch (e) {
        console.log('There was an error, but I will not throw it.');
        console.log('The error\'s message was: ' + e.message)
    }
}

function funcThatThrowsString() {
    throw 'I am a String.';
}

runWithoutThrowing(funcThatThrowsString);

This code runs, runWithoutThrowing in 2nd console.log throws an error, because e.message is undefined. These may seem no big deal, but if your code needs to use certain properties of the Error object, then you need to do a lot of extra work to make sure everything works. If you throw a value other than Error object, you will not get the important information related to the error, such as stack, although this attribute in part JS runtime environment will have.

Error objects can also be used to other objects as you can not throw an error, but only to pass out the error, Node.js in the wrong priority callback is a typical example of this approach, such as Node.js function of fs.readdir :

const fs = require('fs');

fs.readdir('/example/i-do-not-exist', function callback(err, dirs) {
    if (err) {
        // `readdir` will throw an error because that directory does not exist
        // We will now be able to use the error object passed by it in our callback function
        console.log('Error Message: ' + err.message);
        console.log('See? We can use Errors without using try statements.');
    } else {
        console.log(dirs);
    }
});

Further, Error object also can be used when the Promise.reject, making it easier handling Promise failure, such as the following example:

new Promise(function(resolve, reject) {
    reject(new Error('The promise was rejected.'));
}).then(function() {
    console.log('I am an error.');
}).catch(function(err) {
    if (err instanceof Error) {
        console.log('The promise was rejected with an error.');
        console.log('Error Message: ' + err.message);
    }
});

Error stack of cut

Node.js only support this feature, achieved by Error.captureStackTrace, Error.captureStackTrace receiving an object as the first parameter, and the optional function as the second parameter. Its role is to capture the current call stack and its cut, the captured call stack recorded on the stack attribute of an argument, cutting reference point is the second argument, that is, before this function call It will be recorded in the call stack on top of, and not after.

Let us use the code to explain, first of all, the current call stack is captured and put on myObj:

const myObj = {};

function c() {
}

function b() {
    // 把当前调用栈写到 myObj 上
    Error.captureStackTrace(myObj);
    c();
}

function a() {
    b();
}

// 调用函数 a
a();

// 打印 myObj.stack
console.log(myObj.stack);

// 输出会是这样
//    at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
//    at a (repl:2:1)
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)

Call stack above only a -> b, because before we call it b c captured in the call stack. Now the above code slightly modified, and then see what happens:

const myObj = {};

function d() {
    // 我们把当前调用栈存储到 myObj 上,但是会去掉 b 和 b 之后的部分
    Error.captureStackTrace(myObj, b);
}

function c() {
    d();
}

function b() {
    c();
}

function a() {
    b();
}

// 执行代码
a();

// 打印 myObj.stack
console.log(myObj.stack);

// 输出如下
//    at a (repl:2:1) <-- As you can see here we only get frames before `b` was called
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)
//    at emitOne (events.js:101:20)

In this code there, because when we call Error.captureStackTrace is introduced to b, so that after the call stack will be hidden b.

Now you may ask, know that in the end dim? If you want the user to hide the error stack with his business-related (such as inside a library implementation) you can try this technique.

to sum up

The description herein, I believe you are on the call stack JS, Error object error stack have a clear understanding, I encountered an error when not panic. If you have any questions about the contents of the text are welcome in the comments below.

Guess you like

Origin blog.51cto.com/14484771/2433329