異步異常處理
異步異常的特點
由于node的回調(diào)異步特性,無法通過try catch來捕捉所有的異常:
try { process.nextTick(function () { foo.bar(); });} catch (err) { //can not catch it}而對于web服務(wù)而言,其實是非常希望這樣的:
//express風(fēng)格的路由app.get('/index', function (req, res) { try { //業(yè)務(wù)邏輯 } catch (err) { logger.error(err); res.statusCode = 500; return res.json({success: false, message: '服務(wù)器異常'}); }});如果try catch能夠捕獲所有的異常,這樣我們可以在代碼出現(xiàn)一些非預(yù)期的錯誤時,能夠記錄下錯誤的同時,友好的給調(diào)用者返回一個500錯誤。可惜,try catch無法捕獲異步中的異常。所以我們能做的只能是:
app.get('/index', function (req, res) { // 業(yè)務(wù)邏輯 });process.on('uncaughtException', function (err) { logger.error(err);});這個時候,雖然我們可以記錄下這個錯誤的日志,且進(jìn)程也不會異常退出,但是我們是沒有辦法對發(fā)現(xiàn)錯誤的請求友好返回的,只能夠讓它超時返回。
domain
在node v0.8+版本的時候,發(fā)布了一個模塊domain。這個模塊做的就是try catch所無法做到的:捕捉異步回調(diào)中出現(xiàn)的異常。
于是乎,我們上面那個無奈的例子好像有了解決的方案:
var domain = require('domain');//引入一個domain的中間件,將每一個請求都包裹在一個獨立的domain中//domain來處理異常app.use(function (req,res, next) { var d = domain.create(); //監(jiān)聽domain的錯誤事件 d.on('error', function (err) { logger.error(err); res.statusCode = 500; res.json({sucess:false, messag: '服務(wù)器異常'}); d.dispose(); }); d.add(req); d.add(res); d.run(next);});app.get('/index', function (req, res) { //處理業(yè)務(wù)});我們通過中間件的形式,引入domain來處理異步中的異常。當(dāng)然,domain雖然捕捉到了異常,但是還是由于異常而導(dǎo)致的堆棧丟失會導(dǎo)致內(nèi)存泄漏,所以出現(xiàn)這種情況的時候還是需要重啟這個進(jìn)程的,有興趣的同學(xué)可以去看看domain-middleware這個domain中間件。
詭異的失效
我們的測試一切正常,當(dāng)正式在生產(chǎn)環(huán)境中使用的時候,發(fā)現(xiàn)domain突然失效了!它竟然沒有捕獲到異步中的異常,最終導(dǎo)致進(jìn)程異常退出。經(jīng)過一番排查,最后發(fā)現(xiàn)是由于引入了redis來存放session導(dǎo)致的。
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}));
新聞熱點
疑難解答
圖片精選