betway必威-betway必威官方网站
做最好的网站

Node错误管理笔记之挖坑种类教程,异步卓殊的拍

前言

异步异常处理

node是单线程,某一个任务的后续操作,往往采用回调函数(callback)的形式进行定义。Node约定,如果某个函数需要回调函数作为参数,则回调函数是最后一个参数。另外,回调函数本身的第一个参数,约定为上一步传入的错误对象。如果没有发生错误,回调函数的第一个参数就传入null。

前段时间要在项目中加上日志的上报,顺势了解了下怎么在node中完善错误信息的收集。下面话不多说了,来一起看看详细的介绍吧

异步异常的特点

全局对象和全局变量

  • 全局对象:global,process(node所属当前进程),console
  • 全局函数:setTimeout(),clearTimeout(),setInterval(),clearInterval(),require(用于加载模块),Buffer(用于操作二进制数据)
  • 全局变量:__filename(当前运行的脚本文件名),__dirname(当前运行的脚本所在的目录)
  • 伪全局变量(模块内部):module, module.exports, exports

01 Error

由于node的回调异步特性,无法通过try catch来捕捉所有的异常:

模块化结构

模块即文件

  • 加载模块:require("moudle")
  • 输出模块:module.exports
  • 核心模块:http,url,fs,querystring,child_process,util,path,crypto

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

try {
 process.nextTick(function () {
  foo.bar();
 });
} catch (err) {
 //can not catch it
}

异常处理

Node是单线程运行环境,一旦抛出的异常没有被捕获,就会引起整个进程的崩溃。所以,Node的异常处理对于保证系统的稳定运行非常重要。

  • 使用throw语句抛出一个错误对象,即抛出异常。
    try…catch结构,但是,这个结构无法捕获异步运行的代码抛出的异常。因为异步操作会在下一轮事件循环中抛出异常,但是此时catch语句已经运行结束。解决方案:用setTimeout,将错误捕获放在异步操作中。
  • 回调函数
    Node采用的方法,是将错误对象作为第一个参数,传入回调函数。这样就避免了捕获代码与发生错误的代码不在同一个时间段的问题。
  • EventEmitter接口的error事件
    如果没有对error事件部署监听函数,会导致整个应用程序崩溃。

图片 1

而对于web服务而言,其实是非常希望这样的:

其他异常处理方法

  • uncaughtException事件
    只要给uncaughtException配置了回调,Node进程不会异常退出,但异常发生的上下文已经丢失,无法给出异常发生的详细信息。而且,异常可能导致Node不能正常进行内存回收,出现内存泄露。所以,当uncaughtException触发后,最好记录错误日志,然后结束Node进程。
  • unhandledRejection事件
    iojs有一个unhandledRejection事件,用来监听没有捕获的Promise对象的rejected状态。
 process.on('unhandledRejection', function (err, p) {
  console.error(err.stack);
})

02 错误抛出几种方式

//express风格的路由
app.get('/index', function (req, res) {
 try {
  //业务逻辑
 } catch (err) {
  logger.error(err);
  res.statusCode = 500;
  return res.json({success: false, message: '服务器异常'});
 }
});

* Throw*:Javascript 抛出的异常,是以 throw 方法抛出,未必都是 Error 的实例,但通过 nodeJs 或者 js 运行时发生的错误,都是 Error 的实例。

如果try catch能够捕获所有的异常,这样我们可以在代码出现一些非预期的错误时,能够记录下错误的同时,友好的给调用者返回一个500错误。可惜,try catch无法捕获异步中的异常。所以我们能做的只能是:

* EventEmitter*:Nodejs 形式的错误回调,大部分流 & 异步事件都衍生自 EventEmitter 类 || 实例,如 fs, process, stream 等。

app.get('/index', function (req, res) {
 // 业务逻辑 
});

process.on('uncaughtException', function (err) {
 logger.error(err);
});

 Process:程序运行过程中抛出的异常,或由底层库抛出,或是运行中发生的一些 SyntaxError 之类。

这个时候,虽然我们可以记录下这个错误的日志,且进程也不会异常退出,但是我们是没有办法对发现错误的请求友好返回的,只能够让它超时返回。

03 错误捕获几种方式

domain

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

在node v0.8 版本的时候,发布了一个模块domain。这个模块做的就是try catch所无法做到的:捕捉异步回调中出现的异常。

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

于是乎,我们上面那个无奈的例子好像有了解决的方案:

图片 2

var domain = require('domain');

//引入一个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);
});

app.get('/index', function (req, res) {
 //处理业务
});

*EventEmitter *

我们通过中间件的形式,引入domain来处理异步中的异常。当然,domain虽然捕捉到了异常,但是还是由于异常而导致的堆栈丢失会导致内存泄漏,所以出现这种情况的时候还是需要重启这个进程的,有兴趣的同学可以去看看domain-middleware这个domain中间件。

由 Events 模块提供的 EventEmitter 类,基于 Observer 模式做的 publish/subscribe,通过 .on('error', ...)|| .addEventlistener('error', ...) 注册 subscriber,.emit() 发布事件,但会有最大的 maxListener 的限制,可更改。 

诡异的失效

不 show 源码了,特别简单,自己去 look 一下。如 koa 的 app 就是基于 EventEmitter 的扩展,因此可以通过监听 error。

我们的测试一切正常,当正式在生产环境中使用的时候,发现domain突然失效了!它竟然没有捕获到异步中的异常,最终导致进程异常退出。经过一番排查,最后发现是由于引入了redis来存放session导致的。

图片 3

var http = require('http');
var connect = require('connect');
var RedisStore = require('connect-redis')(connect);
var domainMiddleware = require('domain-middleware');

var server = http.createServer();
var app = connect();
app.use(connect.session({
 key: 'key',
 secret: 'secret',
 store: new RedisStore(6379, 'localhost')
}));
//domainMiddleware的使用可以看前面的链接
app.use(domainMiddleware({
 server: server,
 killTimeout: 30000
}));

*Process *

此时,当我们的业务逻辑代码中出现了异常,发现竟然没有被domain捕获!经过一番尝试,终于将问题定位到了:

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

var domain = require('domain');
var redis = require('redis');
var cache = redis.createClient(6379, 'localhost');

function error() {
 cache.get('a', function () {
  throw new Error('something wrong');
 });
}

function ok () {
 setTimeout(function () {
  throw new Error('something wrong');
 }, 100);
}
var d = domain.create();
d.on('error', function (err) {
 console.log(err);
});

d.run(ok);  //domain捕获到异常
d.run(error); //异常被抛出

unhandledRejection :promise 的回调报错,可通过监听该事件 catch,但要注意由于 promise 的 rejection 不知道会在啥时候才发生,所以实际上可能在 unhandledRejection 事件触发后才 catch 了这个信息,对应有 rejectionHandled 事件监听,如下:

奇怪了!都是异步调用,为什么前者被捕获,后者却没办法捕获到呢?

本文由betway必威发布于网页设计,转载请注明出处:Node错误管理笔记之挖坑种类教程,异步卓殊的拍

Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。