之前經常有同學會問到怎麼有些異常無法捕獲到呢?
雖然 connect 已經在 handler 外層加了 try catch ,還是無法捕獲非同步呼叫中產生的異常。
現狀
例如最簡單的 helloworld.js 代碼
var connect = require('connect');var app = connect().use(function (req, res, next) { if (req.url === '/sync_error') { throw new Error('sync error'); } if (req.url === '/async_error') { return process.nextTick(function () { mk2.haha(); }); } res.end('hello, ' + req.method + ' ' + req.url);});process.on('uncaughtException', function (err) { console.error(err);});app.listen(1985);
使用 curl 來簡單測試一下
$ curl localhost:1985/foohello, GET /foo$ curl localhost:1985/sync_errorError: sync error at Object.handle ( .../app.js:33:11) ....
好像還挺正常的,異常也被捕獲了。 繼續測試
$ curl localhost:1985/async_error
沒任何輸出了吧?從伺服器端控制台看到這樣的輸出: 證明觸發了 uncaughtException 事件。
[ReferenceError: mk2 is not defined]
雖然我們能通過 uncaughtException 事件捕獲到非同步呼叫中產生的異常,但是我們沒辦法返回 HTTP 500 異常響應給調用者。 調用者也只能一直hold住,直到請求逾時。
這是非常不好的做法,因為請求量很大,服務進程記憶體會暴漲,會導致進程超出記憶體限制,非正常退出。
改進
任何時候,如果伺服器端出現異常,我們就應該返回HTTP 500 告訴調用者伺服器異常了。
非同步呼叫產生的異常也應該如此處理。
nodejs@0.8 開始,增加了 domain 模組來協助我們更好地處理異常。
讓我們使用 connect-domain 來改進一下之前的 helloworld.js:
var connect = require('connect');var connectDomain = require('connect-domain');var app = connect().use(connectDomain()) // just using connect-domian middleware.use(function (req, res, next) { if (req.url === '/sync_error') { throw new Error('sync error'); } if (req.url === '/async_error') { return process.nextTick(function () { mk2.haha(); }); } res.end('hello, ' + req.method + ' ' + req.url);});process.on('uncaughtException', function (err) { console.error(err);});app.listen(1984);
再看看 connect-domain 的實現,非常簡單:
var domain = require('domain');module.exports = function (handler) { return function domainMiddleware(req, res, next) { var reqDomain = domain.create(); res.on('close', function () { reqDomain.dispose(); }); reqDomain.on('error', function (err) { if (typeof handler === 'function') { handler(err, req, res, next); } else { next(err); } }); reqDomain.run(next); };};
重複剛才的 curl 測試:
$ curl localhost:1984/foohello, GET /foo$ curl localhost:1984/sync_errorError: sync error at Object.handle ( .../app.js:33:11) ....$ curl localhost:1984/async_errorReferenceError: mk2 is not defined at .../app.js:24:7 at process.startup.processNextTick.process._tickCallback (node.js:244:9)
所有請求都有響應了。
伺服器處理序的輸出:
{ [ReferenceError: mk2 is not defined] domain_thrown: true, domain: { domain: null, _events: { error: [Function] }, _maxListeners: 10, members: [] } }
奇怪的連 uncaughtException 事件也觸發了。這按我的思維來說,不應該觸發才對的。
@isaacs 最新的提交已經修複此問題: https://github.com/joyent/node/issues/4375#issuecomment-11691069
總結
應該沒人再說 nodejs 無法捕獲非同步呼叫中出現的異常了吧?