Error handle & Debug

Errors

There are mainly four types of Errors in Node.js:

Error Triggered by
Standard JavaScript errors error codes
System errors operating system
User-specified errors throw method
Assertion errors assert module

Here are the common standard JavaScript errors:

  • EvalError: Thrown when error occurs when calling eval().
  • SyntaxError: Thrown when codes are not conforming to JavaScript syntax style.
  • RangeError: Thrown when out of bounds.
  • ReferenceError: Thrown when referencing undefined variables.
  • TypeError: Thrown when parameter types are error.
  • URIError: Thrown when misusing global URI handling functions.

And the common system errors list can be viewed by os object in Node.js:

  1. const os = require('os');
  2. console.log(os.constants.errno);

When searching interview questions of Node.js, We find that most of them are out-of-date. In a older post Best practices about error handling in NodeJS, which was translated from Joyent’s official blog, we’ve found the words below:

In fact, the only commonly-used case where you’d use try/catch is JSON.parse and other user-input validation functions.

But in nowadays you can easily use try/catch to catch asynchronous exceptions in Node.js. and after using the upgraded v8 engine from Node.js v7.6, the problem that try/catch codes can not be optimized was also solved. Now let’s see the question

How should I handle unexpected errors? Should I use try/catch, domains, or something else?

Here are the error handling methods in Node.js:

  • callback(err, data) Callback agreement
  • throw / try / catch
  • Error event of EventEmitter

Using callback(err, data) to handle errors is cumbersome and does not have compulsion, so we recommend you understand it, but don’t use it. As for the domain module, it’s already half foot into the coffin.

1) Thank co for his leading forward in this domain, now you can use try/catch to protect key position easily, Such as koa’s error processing can be done in the form of middleware, for more details, see Koa error handling. async/await are the same way as koa.

2) Adding error callback for the key object through error listening form of EventEmitter. Such as error events of http server, tcp server and uncaughtException, unhandledRejection of process object.

3) Using Promise to encapsulate asynchronous, and the error handling of it to handle errors.

4) If the methods above can’t play a good role, then you should learn how to Let It Crash gracefully.

Why is the first parameter of cb should be error? And why is the first parameter of some cb is not error, such as http.createServer?

TODO

Error stack is missing

  1. function test() {
  2. throw new Error('test error');
  3. }
  4. function main() {
  5. test();
  6. }
  7. main();

Then you get an error message:

  1. /data/node-interview/error.js:2
  2. throw new Error('test error');
  3. ^
  4. Error: test error
  5. at test (/data/node-interview/error.js:2:9)
  6. at main (/data/node-interview/error.js:6:3)
  7. at Object.<anonymous> (/data/node-interview/error.js:9:1)
  8. at Module._compile (module.js:570:32)
  9. at Object.Module._extensions..js (module.js:579:10)
  10. at Module.load (module.js:487:32)
  11. at tryModuleLoad (module.js:446:12)
  12. at Function.Module._load (module.js:438:3)
  13. at Module.runMain (module.js:604:10)
  14. at run (bootstrap_node.js:394:7)

You can find that the number of rows reported, call hierarchy of test and main function are all displayed clearly in the stack.

When we use timers such as setImmediate to set asynchronously:

  1. function test() {
  2. throw new Error('test error');
  3. }
  4. function main() {
  5. setImmediate(() => test());
  6. }
  7. main();

We find this:

  1. /data/node-interview/error.js:2
  2. throw new Error('test error');
  3. ^
  4. Error: test error
  5. at test (/data/node-interview/error.js:2:9)
  6. at Immediate.setImmediate (/data/node-interview/error.js:6:22)
  7. at runCallback (timers.js:637:20)
  8. at tryOnImmediate (timers.js:610:5)
  9. at processImmediate [as _immediateCallback] (timers.js:582:5)

The error stack only outputs to the line where the function was called in test function, and the call information of main function is lost. That is if you have many layers of nested function calls, it is very hard to trace this asynchronous call when error occurs, because the up-layer stack is already lost. If you’ve used modules such as async, You may also find that the error stack is very long and tortuous, so it’s difficult to locate the error position through the stack.

This won’t be a problem if the project is small / the coder knows all the thing, but it will become a big pain when the project growing bigger and there is more coders. We’ve talked about this issue in the Suggestions for writing new functions section of best practices about error handling in Node.js mentioned above. Errors are packaged layer by layer through the way of using verror so that we can get the key information for locating error in the finally got Error.

Let’s see the download statistics from yesterday (2017-3-13). Last month, the downloads of verror is 1100w, higher than express (1070w). Now you can feel how popular is this way of writing codes.

Defensive programming

It’s not terrible to make mistakes, what makes a terrible mistake is you are not prepared to deal with it————Introduction and skills of defensive programming

let it crash

Let It Crash

uncaughtException

The uncaughtException event of process object will be triggered when the exception is not caught and bubbling to the Event Loop. By default, Node.js will ouput the stack trace information to the stderr and end process for such exceptions, And adding listener to uncaughtException event can override the default behavior, thus not end the process directly.

  1. process.on('uncaughtException', (err) => {
  2. console.log(`Caught exception: ${err}`);
  3. });
  4. setTimeout(() => {
  5. console.log('This will still run.');
  6. }, 500);
  7. // Intentionally cause an exception, but don't catch it.
  8. nonexistentFunc();
  9. console.log('This will not run.');

Using uncaughtException reasonably

The original intention of uncaughtException is to let you do some recycling processing and then process.exit after getting the error. Official comrades have discussed to remove this event. (See issues)

So you need to know uncaughtException is already a non-conventional means, try to avoid using it to handle errors. Because capturing the error through the event does not mean that you can continue to run happily (On Error Resume Next). There is an unhandled exception inside the program, which means that the application is in an unknown state. If you can not properly restore its status, then it is likely to trigger unforeseen problems. (Even worse if using domain, and all kinds of puzzled questions will be produced)

If the error is not caught in the callback listener specified by the .on function, the process of Node.js will be interrupted and return a non-zero exit code, and finally output the corresponding stack information. Otherwise, there will be infinite recursion. In addition, memory crashes / underlying errors also can not be captured, We currently guess the reason is v8/C++ did not deal with the problem, while Node.js was unable to handle it (TODO: We suddenly found this idea has not been verified yet, please help us to verify it if convenient).

So the right way advised by the officials to use uncaughtException is cleaning up the used resources synchronously (file descriptors, handles, and so on) and then process.exit.

Actually It’s not safe to perform a normal restore operation after uncaughtException event. Officials advise you to prepare for a monitor process to do health checks, manage recoveries and restart when necessary (So the officials are reminding you to use tools such as pm2 implicitly).

unhandledRejection

This event will be triggered When a Promise without binded handler is rejected. It is very useful when investigating and tracking Promise not handles reject behavior.

Here are the parameters of the callback of this event:

  • reason <Error> | <any> Rejected reason (Usually Error)
  • p The rejected Promise

Such as:

  1. process.on('unhandledRejection', (reason, p) => {
  2. console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
  3. // application specific logging, throwing an error, or other logic here
  4. });
  5. somePromise.then((res) => {
  6. return reportToUser(JSON.pasre(res)); // note the typo (`pasre`)
  7. }); // no `.catch` or `.then`

The following code also triggers the unhandledRejection event:

  1. function SomeResource() {
  2. // Initially set the loaded status to a rejected promise
  3. this.loaded = Promise.reject(new Error('Resource not yet loaded!'));
  4. }
  5. var resource = new SomeResource();
  6. // no .catch or .then on resource.loaded for at least a turn

In this example case, it is possible to track the rejection as a developer error as would typically be the case for other ‘unhandledRejection’ events. To address such failures, a non-operational .catch(() => { }) handler may be attached to resource.loaded, which would prevent the ‘unhandledRejection’ event from being emitted. Alternatively, the ‘rejectionHandled’ event may be used.

Domain

In the early Node.js, try/catch is unable to capture asynchronous errors, And the error first callback is just an agreement, without mandataries and very cumbersome to write. So in order to catch the exception very well, Node.js introduces domain module in v0.8.

domain is an EventEmitter object, The basic idea of capturing an asynchronous exception is to create a domain, The cb function will inherit the domain of the upper layer, and the errors will be passed through the error events triggered by .emit('error', err) function in current domain, so that asynchronous errors can be forced to capture. (For more details, see Asynchronous exception handling in Node.js and analysis of domain module)

But domain also brought more new problems. Such as dependent modules can not inherit the domain you defined, Causing it can’t cover errors in dependent modules. Furthermore, Many people (especially new bie) didn’t understand memory / asynchronous processes and other issues in Node.js, they didn’t do well when using domain to process errors and let the code continue, this is likely to cause the project to be completely unserviceable (Any problems are possible, And all kinds of shit…)

You can see the latest news about this module at: deprecate domains

Debugger

node-js-survey-debug

Command line debug tool like gdb (Build-in debugger in the image above), it also supports remote debug (like node-inspector, but still in trial). Of course, many developers feel that vscode has maken a better integration to the debug tools.

We recommend reading official document to learn how to use this build-in debugger. If you want to dig deeper, see: Modify the value of a variable in the NodeJS program dynamically

C/C++ Addon

The most painful thing when developing addon in Node.js is the incompatible of C/C++ codes caused by V8 upgrades, and it has lasted for a long time. So someone opened a project named nan to solve this problem.

To learn addon development, We recommend reading: https://github.com/nodejs/node-addon-examples in addition to official document

V8

We are not talking about V8, but V8 module in Node.js. It’s used for opening built-in events and interfaces of V8 engine in Node.js. Because these interfaces are defined by underlying part of V8, so we can’t say it’s absolutely stable.

Interface Description
v8.getHeapStatistics() Get heap informations
v8.getHeapSpaceStatistics() Get heap space informations
v8.setFlagsFromString(string) Settings V8 options dynamicly

v8.setFlagsFromString(string)

This method is used to add additional V8 command line flags. But be cautious, modifying the configuration after the VM starts may cause unpredictable behavior, crash, and data loss; Or nothing.

You can query the available V8 options in the current Node.js environment by node --v8-options command. Furthermore, you can also refer to an unofficial maintenance V8 options list.

Example:

  1. // Print GC events to stdout for one minute.
  2. const v8 = require('v8');
  3. v8.setFlagsFromString('--trace_gc');
  4. setTimeout(function() { v8.setFlagsFromString('--notrace_gc'); }, 60e3);

Memory snapshots

Memory snapshots are commonly used to resolve memory leaks. We recommend you use heapdump to save memory snapshots, and devtool to view memory snapshots. When using heapdump to save memory snapshots, it only contains objects in Node.js (but for node-inspector, there will be front-end variables in the snapshot).

For more details about memory leaks and how to resolve them, see: How to analysis memory leaks in Node.js.

CPU profiling

CPU profiling is commonly used in performance optimization. And there are many third-party tools to do it, but in most cases, the easiest way is using the built-in one in Node.js - V8 internal profiler, it can do interval sampling analysis during the execution of the program.

Using --prof to turn on built-in profilling.

  1. node --prof app.js

It will generate one isolate-0xnnnnnnnnnnnn-v8.log file in the current run directory after the program runs.

You can use --prof-process to generate a report.

  1. node --prof-process isolate-0xnnnnnnnnnnnn-v8.log

And the report is as followed:

  1. Statistical profiling result from isolate-0x103001200-v8.log, (12042 ticks, 2634 unaccounted, 0 excluded).
  2. [Shared libraries]:
  3. ticks total nonlib name
  4. 35 0.3% /usr/lib/system/libsystem_platform.dylib
  5. 27 0.2% /usr/lib/system/libsystem_pthread.dylib
  6. 7 0.1% /usr/lib/system/libsystem_c.dylib
  7. 3 0.0% /usr/lib/system/libsystem_kernel.dylib
  8. 1 0.0% /usr/lib/system/libsystem_malloc.dylib
  9. [JavaScript]:
  10. ticks total nonlib name
  11. 208 1.7% 1.7% Stub: LoadICStub
  12. 187 1.6% 1.6% KeyedLoadIC: A keyed load IC from the snapshot
  13. 104 0.9% 0.9% Stub: VectorStoreICStub
  14. 69 0.6% 0.6% LazyCompile: *emit events.js:136:44
  15. 68 0.6% 0.6% Builtin: CallFunction_ReceiverIsNotNullOrUndefined
  16. 65 0.5% 0.5% KeyedStoreIC: A keyed store IC from the snapshot {2}
  17. 47 0.4% 0.4% Builtin: CallFunction_ReceiverIsAny
  18. 43 0.4% 0.4% LazyCompile: *storeHeader _http_outgoing.js:312:21
  19. 34 0.3% 0.3% LazyCompile: *removeListener events.js:315:28
  20. 33 0.3% 0.3% Stub: RegExpExecStub
  21. 33 0.3% 0.3% LazyCompile: *_addListener events.js:210:22
  22. 32 0.3% 0.3% Stub: CEntryStub
  23. 32 0.3% 0.3% Builtin: ArgumentsAdaptorTrampoline
  24. 31 0.3% 0.3% Stub: FastNewClosureStub
  25. 30 0.2% 0.3% Stub: InstanceOfStub
  26. ...
  27. [C++]:
  28. ticks total nonlib name
  29. 460 3.8% 3.8% _mach_port_extract_member
  30. 329 2.7% 2.7% _openat$NOCANCEL
  31. 199 1.7% 1.7% ___bsdthread_register
  32. 136 1.1% 1.1% ___mkdir_extended
  33. 116 1.0% 1.0% node::HandleWrap::Close(v8::FunctionCallbackInfo<v8::Value> const&)
  34. 112 0.9% 0.9% void v8::internal::BodyDescriptorBase::IterateBodyImpl<v8::internal::StaticScavengeVisitor>(v8::internal::Heap*, v8::internal::HeapObject*, int, int)
  35. 106 0.9% 0.9% _http_parser_execute
  36. 103 0.9% 0.9% _szone_malloc_should_clear
  37. 99 0.8% 0.8% int v8::internal::BinarySearch<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int, int*)
  38. 89 0.7% 0.7% node::TCPWrap::Connect(v8::FunctionCallbackInfo<v8::Value> const&)
  39. 86 0.7% 0.7% v8::internal::LookupIterator::State v8::internal::LookupIterator::LookupInRegularHolder<false>(v8::internal::Map*, v8::internal::JSReceiver*)
  40. ...
  41. [Bottom up (heavy) profile]:
  42. Note: percentage shows a share of a particular caller in the total
  43. amount of its parent calls.
  44. Callers occupying less than 2.0% are not shown.
  45. ticks parent name
  46. 2634 21.9% UNKNOWN
  47. 764 29.0% LazyCompile: *connect net.js:815:17
  48. 764 100.0% LazyCompile: ~<anonymous> net.js:966:30
  49. 764 100.0% LazyCompile: *_tickCallback internal/process/next_tick.js:87:25
  50. 193 7.3% LazyCompile: *createWriteReq net.js:732:24
  51. 101 52.3% LazyCompile: *Socket._writeGeneric net.js:660:42
  52. 99 98.0% LazyCompile: ~<anonymous> net.js:667:34
  53. 99 100.0% LazyCompile: ~g events.js:287:13
  54. 99 100.0% LazyCompile: *emit events.js:136:44
  55. 92 47.7% LazyCompile: ~Socket._writeGeneric net.js:660:42
  56. 91 98.9% LazyCompile: ~<anonymous> net.js:667:34
  57. 91 100.0% LazyCompile: ~g events.js:287:13
  58. 91 100.0% LazyCompile: *emit events.js:136:44
  59. ...
Field Description
ticks Time slice
total The ratio of the current operation to the total time
nonlib Current ratio of non-System library execution time

Coming soon…