new Error(message) 会生成一个 Error 对象
该函数捕获 targetObject 调用栈,只能使用在 V8 引擎上:
//captureStackTrace 会给 targetObject 设置 stack 属性,用于获取 targetObject 的调用栈信息
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // similar to `new Error().stack`
与 new Error().stack 的区别:
- getThis : returns the value of this
- getTypeName : returns the type of this as a string. This is the name of the function stored in the constructor field of this, if available, otherwise the - - - - object's [[Class]]internal property.
- getFunction : returns the current function
- getFunctionName : returns the name of the current function, typically its name property. If a name property is not available an attempt will be made to try to infer a name from the function's context.
- getMethodName : returns the name of the property of this or one of its prototypes that holds the current function
- getFileName : if this function was defined in a script returns the name of the script
- getLineNumber : if this function was defined in a script returns the current line number
- getColumnNumber : if this function was defined in a script returns the current column number
- getEvalOrigin : if this function was created using a call to eval returns a CallSite object representing the location where eval was called
- isToplevel : is this a toplevel invocation, that is, is this the global object?
- isEval : does this call take place in code defined by a call to eval?
- isNative : is this call in native V8 code?
- isConstructor : is this a constructor call?
// 报错默认行为
let oldPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = function (error, callSites) {
return error.toString() + '\n' + callSites.map(callSite => {
return ' -> ' + callSite.getFunctionName() + ' ('
+ callSite.getFileName() + ':'
+ callSite.getLineNumber() + ':'
+ callSite.getColumnNumber() + ')'
}).join('\n')
}
/*do something*/
// 复原默认行为
Error.prepareStackTrace = oldPrepareStackTrace;
可以修改堆栈信息的深度,默认为10。
Node.js 中有很多异步操作,问题出在异步操作,当遇到异步回调就会丢失绑定回调前的调用栈信息:
//这段代码就会丢失 bar 函数的调用栈
var foo = function () {
throw new Error('error!!!')
}
var bar = function () {
setTimeout(foo)
}
bar()
/Users/nswbmw/Desktop/test/app.js:2
throw new Error('error!!!')
^
Error: error!!!
at Timeout.foo [as _onTimeout] (/Users/nswbmw/Desktop/test/app.js:2:9)
at ontimeout (timers.js:469:11)
at tryOnTimeout (timers.js:304:5)
at Timer.listOnTimeout (timers.js:264:5)
不过 Node 8 提供了 async_hooks 模块,该模块可以追踪异步的调用路径,想了解该模块可以看我的博客-学习使用 NodeJs 中 async-hooks 模块。
错误 | 名称 | 触发 |
---|---|---|
Standard JavaScript errors | 标准 JavaScript 错误 | 由错误代码触发 |
System errors | 系统错误 | 由操作系统触发 |
User-specified errors | 用户自定义错误 | 通过 throw 抛出 |
Assertion errors | 断言错误 | 由 assert 模块触发 |
在 Node.js 中错误处理主要有以下几种方法:
readFile('./a.txt', function(err, data){
if(err){
console.log('callback.error', err);
}
});
try{
/*do something*/
throw new Error('throwError')
}catch(err){
console.log('throwerr:', err);
}
const myEmitter = new MyEmitter();
myEmitter.on('error', (err) => {
console.error('whoops! there was an error');
});
myEmitter.emit('emitter.error', new Error('whoops!'));
new Promise.reject('a error').catch(err => {console.log('promise.error:', err)});
let f = async function(){
try {
await doPromise();
}catch(err){
console.log('await.error:', err);
}
}
domain 本身是一个 EventEmitter 对象, 其中文意思是 “域” 的意思, 捕获异步异常的基本思路是创建一个域, cb 函数会在定义时会继承上一层的域, 报错通过当前域的.emit(‘error’, err) 方法触发错误事件将错误传递上去, 从而使得异步错误可以被强制捕获:
//引入一个domain的中间件,将每一个请求都包裹在一个独立的domain中
//domain来处理异常
app.use(function (req,res, next) {
var d = domain.create();
//监听domain的错误事件
d.on('error', function (err) {
logger.error(err);
res.statusCode = 500;
res.json({sucess:false, messag: '服务器异常'});
d.dispose();
});
d.add(req);
d.add(res);
d.run(next);
});
但是 domain 的引入也带来了更多新的问题. 比如依赖的模块无法继承你定义的 domain, 导致你写的 domain 无法 cover 依赖模块报错. 而且, 很多人 (特别是新人) 由于不了解 Node.js 的内存/异步流程等问题, 在使用 domain 处理报错的时候, 没有做到完善的处理并盲目的让代码继续走下去, 这很可能导致项目完全无法维护,目前该模块已经被废弃
uncaughtException/unhandledRejection/rejectionHandled 是 process 进程对象可以监听的几个异常处理的事件:
应尽量避免使用 uncaughtException 事件来处理错误,官方建议的使用 uncaughtException 的正确姿势是在结束进程前使用同步的方式清理已使用的资源 (文件描述符、句柄等) 然后 process.exit
var p = (new Promise(function(resolve, reject){
reject(new Error('首次抛出异步异常'));
// 或者通过 throw 的方式抛出,效果相同
// throw new Error('Error from promise by throw');
}));
//监听未捕获的同步异常事件
process.on('uncaughtException', function(e){
console.error('uncaughtException:Catch in process', e.message);
});
//监听一个当前事件循环中,未被捕获的异常,该异常可能在之后的循环中被捕获
process.on('unhandledRejection', (reason) => {
console.info('unhandledRejection:Catch in process', reason.message);
});
//监听一个Rejected Promise在事件循环的下次轮询或者之后被绑定了一个异常处理函数(catch)时触发
process.on('rejectionHandled', (p) => {
console.info('rejectionHandled:Catch in process', p);
});
setTimeout(function(){
p.catch(function(e){
//console.error('Catch in Promise', e);
throw new Error('再次抛出异步异常');
});
}, 3e3);
(function(){
throw new Error('同步异常');
})()
//上述代码的输出结果为:
/*uncaughtException:Catch in process 同步异常
unhandledRejection:Catch in process 首次抛出异步异常
rejectionHandled:Catch in process Promise {
Error: 首次抛出异步异常
at /Users/zhangdianpeng/projectCode/myProject/MyError/index.js:8:12
at Promise ()
at Object. (/Users/zhangdianpeng/projectCode/myProject/MyError/index.js:7:10)
at Module._compile (module.js:573:30)
at Object.Module._extensions..js (module.js:584:10)
at Module.load (module.js:507:32)
at tryModuleLoad (module.js:470:12)
at Function.Module._load (module.js:462:3)
at Function.Module.runMain (module.js:609:10)
at startup (bootstrap_node.js:158:16) }
unhandledRejection:Catch in process 再次抛出异步异常*/
这种方法平时检测一下还是可以的,真正有难缠的bug的时候会有种暴力穷举的感觉,常用的方法如下:
该模块导出了两个特定的组件:
//全局Console的使用方式:
const output = fs.createWriteStream('./stdout.log');
const errorOutput = fs.createWriteStream('./stderr.log');
// 自定义的简单记录器
const logger = new Console(output, errorOutput);
// 像 console 一样使用
const count = 5;
logger.log('count: %d', count);
console提供的方法:
用于调试断言的模块,常用函数:
let assert = require('assert');
assert(1 === 1, 'is not true');
assert.ok(1 === 1, 'is not true'); // assert() 是 assert.ok 的别名
assert.equal(1, 1, 'is not true')
assert.deepStrictEqual({a: {b: 1}}, {a: {b: 1}}, 'is not ok') // 深度相等判断,原始值使用相等运算符(==)比较, 而对象只测试自身可枚举的属性
assert.doesNotThrow(
() => {
throw new TypeError('错误值');
},
TypeError
);
debug 是一个小巧却非常实用的日志模块,可以根据环境变量决定打印不同类型(或级别)的日志,代码如下:
const normalLog = require('debug')('log')
const errorLowLog = require('debug')('error:low')
const errorNormalLog = require('debug')('error:normal')
const errorHighLog = require('debug')('error:high')
setInterval(() => {
const value = Math.random()
switch (true) {
case value < 0.5: normalLog(value); break
case value >= 0.5 && value < 0.7: errorLowLog(value); break
case value >= 0.7 && value < 0.9: errorNormalLog(value); break
case value >= 0.9: errorHighLog(value); break
default: normalLog(value)
}
}, 1000)
在启动的 node 进程时指定DEBUG环境变量便可以打印相应等级的日志:
DEBUG=*:打印所有类型的日志
DEBUG=log:只打印 log 类型的日志
DEBUG=error:*:打印所有以 error: 开头的日志
DEBUG=error:*,-error:low:打印所有以 error: 开头的并且过滤掉 error:low 类型的日志,即这里只打印一般级别和严重级别的错误日志
一个增强版的 repl,我们有时候想测试某个系统函数或者第三方函数的功能时,或者在 repl 里运行一些测试代码时,这个库很有必要。repl2 会根据一个用户配置(~/.noderc),预先加载模块到 REPL 中,省下了我们手动在原生 REPL 中 require 模块的过程:
{
"lodash": "__",
"moment": "moment",
"validator": "validator"
}
$ noder
> moment().format('YYYY')
'2017'
> __.random(0, 5)
3
> validator.isEmail('[email protected]')
true
repl2 的源码实现也特别简单易懂,具体想了解如何实现,可以看的博客 Nodejs 定制化你自己的REPL
以“No API is the best API”为准则,可以实现无缝替换官方 assert 模块,是目前最好用的断言库
相比其它断言库,最大的优点就是错误信息非常直观:
various types demo:
AssertionError: # test/example2.js:43
assert(types[index].name === bob.name)
| || | | | |
| || | | | "bob"
| || | | Person{name:"bob",age:5}
| || | false
| |11 "alice"
| Person{name:"alice",age:3}
["string",98.6,true,false,null,undefined,#Array#,#Object#,NaN,Infinity,/^not/,#Person#]
--- [string] bob.name
+++ [string] types[index].name
想了解更多,可以参考这篇博客-可能是最好的 JS Assert 库 - 皇帝的新衣
启动 node 脚本带上 inspect 参数可以进入命令行调试界面,常用操作如下:
* `cont`, `c` - 继续执行
* `next`, `n` - 下一步
* `step`, `s` - 跳进函数
* `out`, `o` - 跳出函数
* `pause` - 暂停运行代码(类似开发者工具中的暂停按钮)
断点设置可以在代码中用debugger;设置,也可以在调试的过程中设置:
* `setBreakpoint()`, `sb()` - 在当前行设置断点
* `setBreakpoint(line)`, `sb(line)` - 在指定行设置断点
* `setBreakpoint('fn()')`, `sb(...)` - 在函数体的第一条语句设置断点
* `setBreakpoint('script.js', 1)`, `sb(...)` - 在 script.js 的第 1 行设置断点
* `clearBreakpoint('script.js', 1)`, `cb(...)` - 清除 script.js 第 1 行的断点
* `backtrace`, `bt` - 打印当前执行框架的回溯
* `list(5)` - 列出脚本源代码的 5 行上下文(前后各 5 行)
* `watch(expr)` - 添加表达式到监视列表
* `unwatch(expr)` - 从监视列表移除表达式
* `watchers` - 列出所有监视器和它们的值(每个断点会自动列出),可以监听设置好的参数的值
* `repl` - 打开调试器的 repl,用于在所调试的脚本的上下文中进行执行,在repl中可以直接运行命令,获取值
* `exec expr` - 在所调试的脚本的上下文中执行一个表达式,相当于在repl中运行命令一样
node --inspect app.js
node --inspect-brk app.js 默认在第一行代码就加上断点
具体看文档
npm install node-inspector -g
node-debug app.js
基本上每个 IDE 都集成了对应的调试入口