Node.js中出現(xiàn)未捕獲異常的處理方法
前言
Node.js 程序運(yùn)行在單進(jìn)程上,應(yīng)用開(kāi)發(fā)時(shí)一個(gè)難免遇到的問(wèn)題就是異常處理,對(duì)于一些未捕獲的異常處理起來(lái),也不是一件容易的事情。
未捕獲異常的程序
下面展示了一段簡(jiǎn)單的應(yīng)用程序,如下所示:
const http = require('http');
const PORT = 3000;
const server = http.createServer((req, res) => {
if (req.url === '/error') {
a.b;
res.end('error');
} else {
setTimeout(() => res.end('ok!'), 1000 * 10);
}
});
server.listen(PORT, () => console.log(`port is listening on ${PORT}.`));
運(yùn)行以上程序,在右側(cè)第二個(gè)窗口中執(zhí)行了 /error 路由,因?yàn)闆](méi)有定義 a 這個(gè)對(duì)象,則會(huì)引發(fā)錯(cuò)誤。

進(jìn)程崩潰退出之后導(dǎo)致整個(gè)應(yīng)用程序也將崩潰,左側(cè)是一個(gè)延遲的響應(yīng),也將無(wú)法正常工作。
這是一個(gè)頭疼的問(wèn)題,不要緊,下文我們將會(huì)學(xué)到一個(gè)優(yōu)雅退出的方案。
進(jìn)程崩潰優(yōu)雅退出
關(guān)于錯(cuò)誤捕獲,Node.js 官網(wǎng)曾提供了一個(gè)模塊 domain 來(lái)實(shí)現(xiàn),但是現(xiàn)在已廢棄了所以就不再考慮了。
之前在看 CNPM 這個(gè)項(xiàng)目時(shí)看到了以下關(guān)于錯(cuò)誤退出的一段代碼:
// https://github.com/cnpm/cnpmjs.org/blob/master/worker.js#L18
graceful({
server: [registry, web],
error: function (err, throwErrorCount) {
if (err.message) {
err.message += ' (uncaughtException throw ' + throwErrorCount + ' times on pid:' + process.pid + ')';
}
console.error(err);
console.error(err.stack);
logger.error(err);
}
});
上述使用的是 graceful 這個(gè)模塊,在 NPM 上可以找到。
實(shí)現(xiàn)一個(gè) graceful.js
實(shí)現(xiàn)一個(gè) graceful 函數(shù),初始化加載時(shí)注冊(cè) uncaughtException、unhandledRejection 兩個(gè)錯(cuò)誤事件,分別監(jiān)聽(tīng)未捕獲的錯(cuò)誤信息和未捕獲的 Promise 錯(cuò)誤信息。
const http = require('http');
/**
* graceful
* @param { Number } options.killTimeout 超時(shí)時(shí)間
* @param { Function } options.onError 產(chǎn)生錯(cuò)誤信息會(huì)執(zhí)行該回調(diào)函數(shù)
* @param { Array } options.servers Http Server
* @returns
*/
function graceful(options = {}) {
options.killTimeout = options.killTimeout || 1000 * 30;
options.onError = options.onError || function () {};
options.servers= options.servers || [];
process.on('uncaughtException', error => handleUncaughtException(error, options));
process.on('unhandledRejection', error => handleUnhandledRejection(error, options));
}
handleUncaughtException、handleUnhandledRejection 分別接收相應(yīng)的錯(cuò)誤事件,執(zhí)行應(yīng)用傳入的 onError() 將錯(cuò)誤信息進(jìn)行回傳,最后調(diào)用 handleError()。
const throwCount = {
uncaughtException: 0,
unhandledRejection: 0
};
function handleUncaughtException(error, options) {
throwCount.uncaughtException += 1;
options.onError(error, 'uncaughtException', throwCount.uncaughtException);
if (throwCount.uncaughtException > 1) return;
handleError(options);
};
function handleUnhandledRejection(error, options) {
throwCount.unhandledRejection += 1;
options.onError(error, 'unhandledRejection', throwCount.unhandledRejection);
if (throwCount.unhandledRejection > 1) return;
handleError(options);
}
HandleError 方法為核心實(shí)現(xiàn),首先遍歷應(yīng)用傳入的 servers,監(jiān)聽(tīng) request 事件,在未捕獲錯(cuò)誤觸發(fā)之后,如果還有請(qǐng)求鏈接,則關(guān)閉當(dāng)前請(qǐng)求的鏈接。
之后,執(zhí)行 setTimeout 延遲退出,也就是最大可能的等待之前鏈接處理完成。
function handleError(options) {
const { servers, killTimeout } = options;
// 關(guān)閉當(dāng)前請(qǐng)求的鏈接
for (const server of servers) {
console.log('server instanceof http.Server: ', server instanceof http.Server);
if (server instanceof http.Server) {
server.on('request', (req, res) => {
req.shouldKeepAlive = false;
res.shouldKeepAlive = false;
if (!res._header) {
res.setHeader('Connection', 'close');
}
});
}
}
// 延遲退出
const timer = setTimeout(() => {
process.exit(1);
}, killTimeout);
if (timer && timer.unref) {
timer.unref();
}
}
module.exports = graceful;
應(yīng)用程序中使用上述實(shí)現(xiàn)
加載上述 graceful.js 使用起來(lái)很簡(jiǎn)單只需要在文件尾部,加載 graceful 函數(shù)并傳入相應(yīng)參數(shù)即可。
const graceful = require('./graceful.js');
...
server.listen(PORT, () => console.log(`port is listening on ${PORT}.`));
graceful({
servers: [server],
onError: (error, type, throwErrorCount) => {
console.log('[%s] [pid: %s] [throwErrorCount: %s] %s: %s', new Date(), process.pid, throwErrorCount, type, error.stack || error);
}
});
再次運(yùn)行應(yīng)用程序,看看效果:

這一次,即使右側(cè) /error 路由產(chǎn)生未捕獲異常,也將不會(huì)引起左側(cè)請(qǐng)求無(wú)法正常響應(yīng)。
Graceful 模塊
最后推薦一個(gè) NPM 模塊 graceful,引用文檔中的一句話:“It's the best way to handle uncaughtException on current situations.”
該模塊還提供了對(duì)于 Node.js 中 Cluster 模塊的支持。
安裝
$ npm install graceful -S
應(yīng)用
如果一個(gè)進(jìn)程中有多個(gè) Server,將它們添加到 servers 中即可。
const graceful = require('graceful');
...
graceful({
servers: [server1, server2, restapi],
killTimeout: '15s',
});
總結(jié)
如果你正在使用 Node.js 對(duì)于異常你需要有些了解,上述講解的兩個(gè)異常事件可以做為你的最后補(bǔ)救措施,但是不應(yīng)該當(dāng)作 On Error Resume Next(出了錯(cuò)誤就恢復(fù)讓它繼續(xù))的等價(jià)機(jī)制。
到此這篇關(guān)于Node.js中出現(xiàn)未捕獲異常處理方法的文章就介紹到這了,更多相關(guān)Node.js未捕獲異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
Reference
- nodejs.cn/api/process.html
- www.npmjs.com/package/graceful
相關(guān)文章
深入理解Node.js中通用基礎(chǔ)設(shè)計(jì)模式
大家在談到設(shè)計(jì)模式時(shí)最先想到的就是 singletons, observers(觀察者) 或 factories(工廠方法)。本文重點(diǎn)給大家介紹Node.JS一些基礎(chǔ)模式的實(shí)現(xiàn)方法,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2017-09-09
Node.js DES加密的簡(jiǎn)單實(shí)現(xiàn)
下面小編就為大家?guī)?lái)一篇Node.js DES加密的簡(jiǎn)單實(shí)現(xiàn)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-07-07
解決下載了nodejs但npm?-v沒(méi)有反應(yīng)問(wèn)題的全過(guò)程
最近工作中遇到了個(gè)問(wèn)題,node安裝成功,但npm無(wú)法使用,所以下面這篇文章主要給大家介紹了關(guān)于下載了nodejs但npm?-v沒(méi)有反應(yīng)問(wèn)題解決的相關(guān)資料,需要的朋友可以參考下2022-08-08
Node.js中,在cmd界面,進(jìn)入退出Node.js運(yùn)行環(huán)境的方法
今天小編就為大家分享一篇Node.js中,在cmd界面,進(jìn)入退出Node.js運(yùn)行環(huán)境的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
詳解如何優(yōu)雅在webpack項(xiàng)目實(shí)現(xiàn)mock服務(wù)器
這篇文章主要為大家介紹了詳解如何優(yōu)雅在webpack項(xiàng)目實(shí)現(xiàn)mock服務(wù)器,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
node中modules.exports與exports導(dǎo)出的區(qū)別
這篇文章主要介紹了node中modules.exports與exports導(dǎo)出的區(qū)別,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Nodejs實(shí)現(xiàn)多人同時(shí)在線移動(dòng)鼠標(biāo)的小游戲分享
這篇文章主要介紹了Nodejs實(shí)現(xiàn)多人同時(shí)在線移動(dòng)鼠標(biāo)的小游戲分享,本文給出了服務(wù)器端和客戶端代碼以及運(yùn)行方法,需要的朋友可以參考下2014-12-12

