Node 错误处理之挖坑系列

一. Error

    JS 中的 Error 对象. 包含了错误的具体信息,包括 name、message、错误堆栈 stack 等。可以以 new Error 方式创建实例抛出,或调用 Error.captureStackTrace 为已有对象添加 stack 错误堆栈信息 而后抛出
clipboard.png

二. 错误抛出几种方式
  • Throw
    Javascript 抛出的异常,是以 throw 方法抛出,未必都是 Error 的实例,但通过 nodeJs 或者 js 运行时发生的错误,都是 Error 的实例
  • EventEmitter
    Nodejs 形式的错误回调,大部分流 & 异步事件都衍生自 EventEmitter 类 || 实例,如 fs, process, stream 等
  • Process
    程序运行过程中抛出的异常,或由底层库抛出,或是运行中发生的一些 SyntaxError 之类
三. 错误捕获几种方式

try .. catch

常用的一种捕获错误方式,浏览器 || node 环境均适用

缺点:只针对同步异常有效

    try {
        throw new Error('Something went wrong here')
    } catch (err) {
        console.log(err)
    }

EventEmitter

    由 Events 模块提供的 EventEmitter 类,基于 Observer 模式做的 publish/subscribe,通过 .on('error', ...) || .addEventlistener('error', ...) 注册 subscriber,.emit() 发布事件,但会有最大的 maxListener 的限制,可更改。
    不 show 源码了,特别简单,自己去 look 一下。如 koa 的 app 就是基于 EventEmitter 的扩展,因此可以通过监听 error

    class Koa extends EventEmitter {...}
    
    let app = new Koa()
    app.emit('error', ..)
    app.on('error', ...)

Process

Process 进程对象也是 EventEmitter 的实例,可通过如下两事件监听 error

  • unhandledRejection
        promise 的回调报错,可通过监听该事件 catch,但要注意由于 promise 的 rejection catched 不知道会在啥时候才发生,所以实际上可能在 unhandledRejection 事件触发后才 catch 了这个信息,对应有 rejectionHandled 事件监听,如下:
    process.on('rejectionHandled', p => {
        console.log('It has been handled')
    })
    
    process.on('unhandledRejection', (reason, p) => {
        console.log('Went here')
    })
    
    let test = new Promise((resolve, reject) => {
        let err = new Error('Just for a test')
        err.name = 'TestError'
        reject(err)
    })
    
    setTimeout(() => {
        test.catch(err => {
            console.log('Excample work!')
            console.log(err)
        })
    }, 10000)

    // 打印出来的信息顺序是:
    // Went here
    // It has been handled
    // Excample work!
    // { TestError: Just for a test
    // ....(stack message here) }

(承上)
uncaughtException
    其余 js 运行中发生 || 抛出的未捕获错误,均可通过监听该事件解决,若不进行该事件的监听,发生异常时,会直接导致程序 crash
    但不建议用这种方式 catch,程序运行中的错误 更应该是抛出来,所有的 error 都 catch 的话,岂不是程序都可以看成无 bug 了
    but 在打错误日志的时候是需要 catch 上报日志的,但是在上报完后,需要继续把 error throw,对于 uncaughtException callback 中抛出的异常不会再捕获,而是以非 0 的状态码 exit

    // Promise Rejection
    process.on('unhandledRejection', err => {
        process.nextTick(() => { throw err }))
    })
    // 终极 boss
    process.on('uncaughtException', err => {...})
};
四.小结

以上以一张图为总结:clipboard.png

五. 源码解读

补充之前没补充完的内容,下图为 node 中对于 process 的初始化等系列流程

  • node.cc 其实是 node 运行主要的文件,其中定义了三个重载函数 Start,调用顺序为 3 → 2 → 1,每个函数参数不同处理不同的逻辑;
  • isolate->AddMessageListener 的监听事件 OnMessage 会在 js 运行发生错误时触发,嗯,是的,只有 error 才会传递 ;这很好的解释了为什么 process 监听 uncaughtException 就可以监听到所有的抛出的非 promise 异常;
  • OnMessage 调用了 FatalException 函数,FatalException 引用 process._fatalException 并传入 error (env 是 env.h 中声明的 Environment 类实例,fatal_exception_string 也是其中定义的,返回值为 '_fatalException');
    以下为 FatalException 函数的部分内容
void FatalException(Isolate* isolate,
                    Local<Value> error,
                    Local<Message> message) {
...(省略)
  Local<String> fatal_exception_string = env->fatal_exception_string(); // '_fatalException'
  Local<Function> fatal_exception_function =
      process_object->Get(fatal_exception_string).As<Function>();
...(省略)
if (exit_code == 0) {
    TryCatch fatal_try_catch(isolate); // 调用 v8::TryCatch 
    
    // Do not call FatalException when _fatalException handler throws
    fatal_try_catch.SetVerbose(false); // 关键点

    // this will return true if the JS layer handled it, false otherwise
    Local<Value> caught =
        fatal_exception_function->Call(process_object, 1, &error); // 运行 process._fatalException 函数

    if (fatal_try_catch.HasCaught()) { // 捕获错误
      // the fatal exception function threw, so we must exit
      ReportException(env, fatal_try_catch);
      exit_code = 7;
    }

    if (exit_code == 0 && false == caught->BooleanValue()) {
      ReportException(env, error, message);
      exit_code = 1;
    }
  }
...(省略)
}
  • 在调用 _fatalException 函数前,先调用 v8::TryCatch::setVerbose 把 verbose 设置为 false,则运行时抛出的异常就不会再触发 FatalException ;在捕获到运行错误后,把 exit_code 设为 7,并返回;这就解释了 为什么在 uncaughtException 时抛出的异常不会再重新触发回调,要知道 EventEmitter 可没帮你做这样的事情
  • _fatalException 在触发 "uncaughtException" 事件前其实是会优先检查 domain 是否存在,当 domain 不存在时才会调用 uncaughtException 的,但 domain 这个 api 已经被废除了,也就不累述了
  • unhandledRejection 事件的触发整体思路也差不多,首先会调用 SetupPromises 初始化,然后调用 v8::Isolate::SetPromiseRejectCallback 进行监听 ... 并且跟 uncaughtException 不同的是 unhandledRejection 的触发只会打印 warning 并不会把整个程序给 crash 了
六. END

若本文有误,欢迎随时指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值