资讯专栏INFORMATION COLUMN

Node 错误处理之挖坑系列

afishhhhh / 1783人阅读

摘要:一中的对象包含了错误的具体信息,包括错误堆栈等。不源码了,特别简单,自己去一下。

一. Error

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

二. 错误抛出几种方式

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 => {...})
};
四.小结

以上以一张图为总结:

五. 源码解读

补充之前没补充完的内容,下图为 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 error,
                    Local message) {
...(省略)
  Local fatal_exception_string = env->fatal_exception_string(); // "_fatalException"
  Local fatal_exception_function =
      process_object->Get(fatal_exception_string).As();
...(省略)
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 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

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

参考链接
V8 API Reference Guide

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91927.html

相关文章

  • 关于前端接口测试的探索和挖坑

    摘要:本文主要关注的是接口测试。所谓接口测试,就是检查系统提供的接口是否符合事先撰写的接口文档。作为接口测试的解决方案,我们必须具备通用性与易用性。 开始 最近几年,前端测试渐渐被人重视,相关的框架和方法已经比较成熟。断言库有should, expect, chai。 单元测试框架有mocha, jasmine, Qunit。 模拟浏览器测试环境有Phantomjs, Slimerjs。 集...

    Crazy_Coder 评论0 收藏0
  • 关于前端接口测试的探索和挖坑

    摘要:本文主要关注的是接口测试。所谓接口测试,就是检查系统提供的接口是否符合事先撰写的接口文档。作为接口测试的解决方案,我们必须具备通用性与易用性。 开始 最近几年,前端测试渐渐被人重视,相关的框架和方法已经比较成熟。断言库有should, expect, chai。 单元测试框架有mocha, jasmine, Qunit。 模拟浏览器测试环境有Phantomjs, Slimerjs。 集...

    zxhaaa 评论0 收藏0
  • node学习系列基础(二)

    摘要:由于这种特性,某一个任务的后续操作,往往采用回调函数的形式进行定义。另外,回调函数本身的第一个参数,约定为上一步传入的错误对象。这种写法有一个很大的好处,就是说只要判断回调函数的第一个参数,就知道有没有出错,如果不是,就肯定出错了。 REPL环境 在命令行键入node命令,后面没有文件名,就进入一个Node.js的REPL环境(Read–eval–print loop,读取-求值-输出...

    zhaot 评论0 收藏0
  • 迪米特法则

    摘要:个人博客原文迪米特法则设计模式六大原则之五迪米特法则。老师便给同学们讲解了这个例子,让学生感受一番迪米特法则。总结迪米特法则主要讲述的观点是高内聚低耦合。 个人博客原文:迪米特法则 showImg(https://segmentfault.com/img/remote/1460000017779272?w=960&h=520); 设计模式六大原则之五:迪米特法则。 简介 姓名:迪米特法...

    OnlyMyRailgun 评论0 收藏0
  • 深入了解JavaScript 中的For循环详解

    摘要:将品牌的标价全部加苏南的专栏交流公众号不会对空数组进行检测。方法用于调用数组的每个元素,并将元素传递给回调函数。 showImg(https://segmentfault.com/img/bVblSSO?w=1008&h=298); 前言: ​ 今天我想分享一个有关于循环筛选的知识点,也许是前端小白的你首先想到的是用for循环做筛选,但我这种小菜鸟想到的就是map(工作中很喜欢...

    linkin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<