异常错误应该被分为两种状况:操做失败和程序员失误html
这是正确编写的程序在运行时产生的错误。它并非程序的Bug,反而常常是其它问题。node
例如:系统自己(内存不足或者打开文件数过多),系统配置(没有到达远程主机的路由),网络问题(端口挂起),远程服务(500错误,链接失败)。具体状况以下:
git
链接不到服务器 没法解析主机名 无效的用户输入 请求超时 服务器返回500 套接字被挂起 系统内存不足
这是程序里的Bug。这些错误每每能够在调试阶段经过修改代码避免。它们永远都无法被有效的处理,而是应该在程序员变编程的时候注意,例如:
程序员
读取 undefined 的一个属性 调用异步函数没有指定回调 该传对象的时候传了一个字符串 该传IP地址的时候传了一个对象
人们把操做失败和程序员的失误都称为“错误”,但其实它们很不同。操做失败是全部正确的程序应该处理的错误情形,只要被妥善处理它们不必定会预示 着Bug或是严重的问题。“文件找不到”是一个操做失败,可是它并不必定意味着哪里出错了。它可能只是表明着程序若是想用一个文件得事先建立它。github
与之相反,程序员失误是不折不扣的Bug。这些情形下你会犯错:忘记验证用户输入,敲错了变量名,诸如此类。这样的错误根本就无法被处理,若是能够,那就意味着你用处理错误的代码代替了出错的代码。mongodb
这样的区分很重要:操做失败是程序正常操做的一部分。而由程序员的失误则是Bug。数据库
有的时候,你会在一个Root问题里同时遇到操做失败和程序员的失误。HTTP服务器访问了未定义的变量时奔溃了,这是程序员的失误。当前链接着的客户端会在程序崩溃的同时看到一个ECONNRESET
错误,在NodeJS里一般会被报成“Socket Hang-up”。对客户端来讲,这是一个不相关的操做失败, 那是由于正确的客户端必须处理服务器宕机或者网络中断的状况。express
相似的,若是不处理好操做失败, 这自己就是一个失误。举个例子,若是程序想要链接服务器,可是获得一个ECONNREFUSED错误,而这个程序没有监听套接字上的error
事件,而后程序崩溃了,这是程序员的失误。链接断开是操做失败(由于这是任何一个正确的程序在系统的网络或者其它模块出问题时都会经历的),若是它不被正确处理,那它就是一个失误。编程
理解操做失败和程序员失误的不一样, 是搞清怎么传递异常和处理异常的基础。明白了这点再继续往下读json
注:若是想有更好的理解,请读参考文档1
请你们读完这篇文章 Node.js 异步异常的处理与domain模块解析
domain.create(): 返回一个domain对象 domain.run(fn): 在domain上下文中执行一个函数,并隐式绑定全部事件,定时器和低级的请求。 domain.members: 已加入domain对象的域定时器和事件发射器的数组。 domain.add(emitter): 显式的增长事件 domain.remove(emitter): 删除事件 domain.bind(callback): 以return为封装callback函数 domain.intercept(callback): 同domain.bind,但只返回第一个参数 domain.enter(): 进入一个异步调用的上下文,绑定到domain domain.exit(): 退出当前的domain,切换到不一样的链的异步调用的上下文中。对应domain.enter() domain.dispose(): 释放一个domain对象,让node进程回收这部分资源
更多的请你们看官网学习
①domain就是来捕获同步和异步的异常的。
②咱们经过中间件的形式,引入domain来处理异步中的异常。固然,domain虽然捕捉到了异常,可是仍是因为异常而致使的堆栈丢失会致使内存泄漏,因此出现这种状况的时候仍是须要重启这个进程的,有兴趣的同窗能够去看看domain-middleware这个domain中间件。
③domain嵌套:咱们可能会外层有domain的状况下,内层还有其余的domain,使用情景能够在官网文档中找到
按照可预测和不可预测分
按照同步和异步分
按照监听和请求分
通过以上三个部分的介绍,咱们就有了咱们的解决方案:
①捕获操做失败并做记录和处理,程序员失误应在调试阶段解决。
②对可预想到的操做失败要作有效的处理(用户提示,日志记录)保证程序的健壮性。
③对不可预料的错误要作日志记录,邮件提示,错误分析,进程守护。
// 初始化路由配置,必定要在404前面初始化 require('./config/router')(app); // 404错误处理 app.use(function(req, res, next) { //var err = new Error('Not Found'); //err.status = 404; //next(err); res.statusCode = 404; res.json({sucess:false, message: '请求路径不存在',err:''}); });
普通的同步异常可预知的,用try-catch-final已经能够很好的处理了。
function sync_error() { var r = Math.random() * 10; console.log("random num is " + r); if (r > 5) { throw new Error("Error: random num" + r + " > 5"); } } setInterval(function () { try { sync_error(); } catch (err) { console.log(err); } }, 1000)
对一些操做要加上事件监听,而后作记录并相应的处理。
例如监听数据库链接断开的异常
//mongodb链接监视 mongoose.connection.on('connected',function(){ console.info("mongoose contected to"+config.DB); }); mongoose.connection.on('error',function(err){ console.info("mongoose contection error"+err); }); mongoose.connection.on('disconnected',function(){ console.info(config.DB+" mongoose disconnected"); }); process.on('SIGINT', function () { mongoose.connection.close(function () { console.info("mongoose disconnected through app termination"); process.exit(0); }); });
例如监听https请求的监听事件
var https = require('https'); var url=require('url'); module.exports = function(app){ app.get('/get', function(req, res, next) { //node是后台语言,因此在请求的时候不会遇到跨域问题 //本身定义option有些麻烦,不如这样处理 var regUrl = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=23&code=33"; var option = url.parse(regUrl); //options 能够是一个对象或字符串。若是 options 是字符串,它会自动被 url.parse() 解析。 var request=https.request(option,function(response){ response.on('data',function(chunk){ res.send("get请求的结果:"+chunk.toString()); }); response.on('end',function(chunk){ response.setEncoding('utf8'); console.log('get请求结束了。'); console.log(chunk); }); }); //这点事请求的异常处理,能够写进日志,也能够跟上面同样链式编程,这点的处理和中间件的怎么处理 request.on('error', function(e) { console.log('problem with request: ' + e.message); throw e;//能够上抛到中间件处理 }); //请求结束 request.end(); }); };
例如express中的服务器监听
/** * Get port from environment and store in Express. */ var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ var server = http.createServer(app); /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening); /** * Normalize a port into a number, string, or false. */ function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } /** * Event listener for HTTP server "error" event. */ function onError(error) { if (error.syscall !== 'listen') { throw error; } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; } } /** * Event listener for HTTP server "listening" event. */ function onListening() { var addr = server.address(); var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); }
异步异常的捕获是经过回调函数中的err参数
function async(fn, callback) { // Code execution path breaks here. setTimeout(function () { try { callback(null, fn()); } catch (err) { callback(err); } }, 0); } async(null, function (err, data) { if (err) { console.log('Error: %s', err.message); } else { // Do something. } });
上面的方法对一些能够预计到的错误均可以进行到捕获,但还有一些不可预计的能够经过domain捕获,domain能够写在每一个js文件中,也能够像下面这样,用中间件拦截每个请求。
domain的使用请参读2.1
// 全部请求异常处理 app.use(function (req,res, next) { var serverDomain = domain.create(); //监听domain的错误事件 serverDomain.on('error', function (err) { //logger.error(err); // todo:日志记录,返回错误的信息要详细 res.statusCode = 500; res.json({sucess:false, message: '服务器异常',err:err.message}); serverDomain.dispose(); }); serverDomain.add(req); serverDomain.add(res); serverDomain.run(next); });
4.5中虽然用了中间件捕获全部的请求,可是服务器的建立我没有包在domain中,你们能够把服务器建立也写在domain中,这就是2.3中提到的domain嵌套,但个人解决办法是,在服务器中进行进程级别的错误捕获。
//进程级别的异常捕获 process.on('uncaughtException', function (err) { console.log('进程级别的错误:'+err.message); });
上面作了各个级别的错误处理,但仍是要给node加上进程守护。你能够本身写本身的进程守护,也能够用 pm2 forver 这些第三方工具进行监控。些模块有很好的日志,部署,监控功能,我以后也会有博客专门介绍使用。
网上有人说用一个第三方插件就会多一份风险,因此我用了本身写的。
/** * Created by shiguoqing on 2015/8/24. */ var fork = require('child_process').fork; var cpus = require('os').cpus(); //保存被子进程实例数组 var workers = []; //这里的被子进程理论上能够无限多 var appsPath = ['./bin/server']; var createWorker = function(appPath){ //保存fork返回的进程实例 var worker = fork(appPath); //监听子进程exit事件 worker.on('exit',function(){ console.log('worker:' + worker.pid + 'exited'); delete workers[worker.pid]; createWorker(appPath); }); workers[worker.pid] = worker; console.log('Create worker:' + worker.pid); }; //启动全部子进程 for (var i = appsPath.length - 1; i >= 0; i--) { createWorker(appsPath[i]); } //父进程退出时杀死全部子进程 process.on('exit',function(){ for(var pid in workers){ workers[pid].kill(); } });
如今这个只能保证子进程重启,可是父进程若是挂了就完蛋了,因此你能够把上面的代码写成,父进程本身挂了能够重启本身。个人程序是以后要在外面加上pm2
Todo:父进程本身挂了重启本身的代码
由于博客文字限制,请你们移步看这段文字
总结:
以上的这些话都代表,node中的异常可能会引发内存的变化。正确的作法是:针对发生异常的请求返回一个错误代码 - 出错的Worker再也不接受新的请求 - 退出关闭Worker进程。
个人程序中是这样处理的,domain中间件捕获到的都是请求的错误,捕获异常以后分析一下。而进程级别捕获捕获到的异常要作记录,邮件提醒,分析,退出worker,重启服务。
咱们异常处理返回的都是一个json对象。这样容易扩展。
{sucess:false, message: '服务器异常',err:err.message,code:'500'}
这种restful的api最好返回状态码,先后台经过约定去读取错误信息,前面的本身抛出的错误最好也抛出自定义的状态码。
100 "continue" 101 "switching protocols" 102 "processing" 200 "ok" 201 "created" 202 "accepted" 203 "non-authoritative information" 204 "no content" 205 "reset content" 206 "partial content" 207 "multi-status" 300 "multiple choices" 301 "moved permanently" 302 "moved temporarily" 303 "see other" 304 "not modified" 305 "use proxy" 307 "temporary redirect" 400 "bad request" 401 "unauthorized" 402 "payment required" 403 "forbidden" 404 "not found" 405 "method not allowed" 406 "not acceptable" 407 "proxy authentication required" 408 "request time-out" 409 "conflict" 410 "gone" 411 "length required" 412 "precondition failed" 413 "request entity too large" 414 "request-uri too large" 415 "unsupported media type" 416 "requested range not satisfiable" 417 "expectation failed" 418 "i'm a teapot" 422 "unprocessable entity" 423 "locked" 424 "failed dependency" 425 "unordered collection" 426 "upgrade required" 428 "precondition required" 429 "too many requests" 431 "request header fields too large" 500 "internal server error" 501 "not implemented" 502 "bad gateway" 503 "service unavailable" 504 "gateway time-out" 505 "http version not supported" 506 "variant also negotiates" 507 "insufficient storage" 509 "bandwidth limit exceeded" 510 "not extended" 511 "network authentication required"
Node下自定义错误类型
https://cnodejs.org/topic/52090bc944e76d216af25f6f
这篇文章能够读读,写了不少基础的东西
http://blog.csdn.net/cike110120/article/details/12916573
一、翻译 - NodeJS错误处理最佳实践 这篇文章请读完,详细的阐释了程序员失误,操做失败
二、Node.js十大常见的开发者错误 你们最好看一看
http://blog.fens.me/nodejs-core-domain/
http://www.cnblogs.com/cbscan/articles/3826461.html
https://cnodejs.org/topic/516b64596d38277306407936
一、本博客中的文章摘自网上的众多博客,仅做为本身知识的补充和整理,并分享给其余须要的coder,不会用于商用。
二、由于不少博客的地址看完没有及时作保存,因此不少不会在这里标明出处。