Node.js 跟 JavaScript同样,同步代码中的异常咱们能够经过 try catch 来捕获.html
但异步代码呢? 咱们来看一个 http server 启动的代码,这个也是个典型的异步代码。web
const http = require('http') try { const server = http.createServer(function (req, res) { console.log('来了') throw new Error('hi') res.end('helo') }) server.listen(3002) } catch (err) { console.log('出错了') }
咱们发现异步代码的异常没法直接捕获。这会致使 Node.js 进程退出。最明显的就是 web server 直接挂掉了。promise
异步代码也有解决办法,咱们直接把try catch 写在异步代码的回调里面:浏览器
const http = require('http') try { const server = http.createServer(function (req, res) { try { throw new Error('hi') } catch (err) { console.log('出错了') } res.end('helo') }) server.listen(3002) } catch (err) { console.log('出错了') }
这样也能catch到错误。网络
然而业务代码很是复杂,并非全部的状况咱们都能预料到。好比在try...catch以后又出现一个throw Error. app
全部没有catch 的Error都会往上冒泡直到变成一个全局的 uncaughtException。 Node.js里对未捕获的异常会检查有没有监听该事件,若是没有就把进程退出:异步
function _MyFatalException(err){ if(!process.emit('uncaughtException',err)){ console.error(err.stack); process.emit('exit',1); } }
所以,防止异步回调异常致使进程退出的办法仿佛就是监听该事件函数
process.on('uncaughtException', function(err) { console.log('出错了,我记录你,并吃掉你') }) const http = require('http') try { const server = http.createServer(function (req, res) { try { throw new Error('hi') } catch (err) { console.log('出错了') } throw new Error('有一个error') res.end('helo') }) server.listen(3002) } catch (err) { console.log('出错了') }
这样进程不会退出。但 极其不优雅
。 由于 uncaughtException 中没有了req和res上下文,没法友好响应用户。另外可能形成内存泄漏(具体参考网络其余资料)ui
所以,uncaughtException 适合用来作Node.js 整个应用最后的兜底。(记录日志or重启服务)this
若是使用了promise,且非异步reject了。在 Node.js 中,这个promise reject 行为会在控制台打印,但目前Node版本不会形成进程退出,也不会触发全局 uncaughtException.
promise最有争议的地方就是当一个promise失败可是没有rejection handler处理错误时静默失败。不过浏览器和Node.js都有相应的处理机制,二者大同小异,都是经过事件的方式监听. 有两个全局事件能够用来监听 Promise 异常:
最好的处理方式,就是应该感知到本身业务代码中的异常。这样的话,不管业务开发人员本身处理了仍是没处理,都能在应用上层catch到进行日志记录。 更佳的状况是:在感知到错误后,能给浏览器一些默认的提示。
但是业务代码里有同步有异步,如此复杂的代码如何能所有cover住呢?
这个会有一些技巧:好比假设咱们的业务代码所有被包裹在本身的一个Promise中,且业务代码的每个异步函数均可以被咱们注入catch回调。在这样完美的状况下,咱们就能在最外层捕获内部发生的全部异常了。
Koa 就是这么干的。Koa1 用 co来运行中间件,co就能够把generator运行起来且捕获其中的异步错误。想了解具体原理的,可能要去看更详细的资料了
这种方式任何JavaScript程序均可以使用,是业务开发人员本身要作的。很少说了
因为Koa是洋葱模型,所以能够在业务逻辑的前置中间件里捕获后面中间件的错误。这里是基于 yield 异步异常能够被try catch的机制。例如:
app.use(function *(next) { try { yield next; } catch (err) { console.log('哇哈 抓到一个错误') // 友好显示给浏览器 this.status = err.status || 500; this.body = err.message; this.app.emit('error', err, this); } });
实际上,上述中间件的工做 ctx.onerror 已经作了。 Koa 内核会自动把中间件的错误交给 ctx.onerror 处理,所以这个中间件我感受不必写了(除非要自定义这个默认的错误处理逻辑)。
若是全部中间件都没有捕获到某个异常,那么co会捕获到。co会调用context对象的onerror, 从而作一些处理(例如返回给浏览器500错误),同时触发 app.onerror
所以,在app.onerror里,你能够作些日志记录或自定义响应
若是 Koa 都没有捕获到异常,那么就由Node来兜底了。不过这个通常不会发生,除非你在app.onerror里还要扔出异常(然而这是个promise异常,也不会触发uncaughtException)。
在 Koa1 中间件里,你可使用 this.throw(status, msg)
抛出异常。 Koa的底层其实本质上会使用 http-errors模块包装这个Error, 并直接 throw这个异常。
如下是 this.throw 函数源码:
// 将你传递的错误码和msg包装为一个 Error对象 throw: function(){ throw createError.apply(null, arguments); }
其中 createError函数至关于:
var err = new Error(msg); err.status = status; throw err; // 包装后再抛出,ctx.onerror才能正确响应错误码给浏览器,不然都是500
所以 中间件 中你调用 this.throw 函数实际上就是真的 throw了一个异常,最终会致使 co 异常。
因为前文讲到的 Koa co 错误捕获机制(co-->catch-->ctx.onerror-->app.onerror),所以,你在任何中间件中throw的异常均可以被app.onerror捕获到。
co在运行generator时,若是某个yield右侧又是一个generator,那么co也会递归地去运行它。固然也会捕获这个嵌套的异步异常。但有些状况下嵌套异步会逃出一个异步的错误检测机制。
好比在Promise里作了另一个异步操做, 在另外的异步操做里抛出了异常。
var fn = function () { return new Promise (function (resolve, reject) { setTimeout(function(){throw new Error('inner bad')}) }) }
这个异常,Promise就没法catch到。 一样,在generator里若是用了这样的方式,异常也会逃逸致使没法捕获。
问题:逃逸出Koa 的 co异步调用链的代码,会致使co没法catch异常。
不如去看看egg怎么作的吧