如何處理 Node.js 中出現的未捕獲異常?
Node.js 程式執行在單程式上,應用開發時一個難免遇到的問題就是異常處理,對於一些未捕獲的異常處理起來,也不是一件容易的事情。
未捕獲異常的程式
下面展示了一段簡單的應用程式,如下所示:
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}.`));
執行以上程式,在右側第二個視窗中執行了 /error 路由,因為沒有定義 a 這個物件,則會引發錯誤。
程式崩潰退出之後導致整個應用程式也將崩潰,左側是一個延遲的響應,也將無法正常工作。
這是一個頭疼的問題,不要緊,下文我們將會學到一個優雅退出的方案。
程式崩潰優雅退出
關於錯誤捕獲,Node.js 官網曾提供了一個模組 domain 來實現,但是現在已廢棄了所以就不再考慮了。
之前在看 這個專案時看到了以下關於錯誤退出的一段程式碼:
//
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);
}
});
上述使用的是 這個模組,在 NPM 上可以找到。
實現一個 graceful.js
實現一個 graceful 函式,初始化載入時註冊 uncaughtException、unhandledRejection 兩個錯誤事件,分別監聽未捕獲的錯誤資訊和未捕獲的 Promise 錯誤資訊。
const http = require('http');
/**
* graceful
* @param { Number } options.killTimeout 超時時間
* @param { Function } options.onError 產生錯誤資訊會執行該回撥函式
* @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 分別接收相應的錯誤事件,執行應用傳入的 onError() 將錯誤資訊進行回傳,最後呼叫 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 方法為核心實現,首先遍歷應用傳入的 servers,監聽 request 事件,在未捕獲錯誤觸發之後,如果還有請求連結,則關閉當前請求的連結。
之後,執行 setTimeout 延遲退出,也就是最大可能的等待之前連結處理完成。
function handleError(options) {
const { servers, killTimeout } = options;
// 關閉當前請求的連結
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;
應用程式中使用上述實現
載入上述 graceful.js 使用起來很簡單隻需要在檔案尾部,載入 graceful 函式並傳入相應引數即可。
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);
}
});
再次執行應用程式,看看效果:
這一次,即使右側 /error 路由產生未捕獲異常,也將不會引起左側請求無法正常響應。
Graceful 模組
最後推薦一個 NPM 模組 ,引用文件中的一句話:“It’s the best way to handle uncaughtException on current situations.”
該模組還提供了對於 Node.js 中 Cluster 模組的支援。
安裝
$ npm install graceful -S
應用
如果一個程式中有多個 Server,將它們新增到 servers 中即可。
const graceful = require('graceful');
...
graceful({
servers: [server1, server2, restapi],
killTimeout: '15s',
});
總結
如果你正在使用 Node.js 對於異常你需要有些瞭解,上述講解的兩個異常事件可以做為你的最後補救措施,但是不應該當作 On Error Resume Next(出了錯誤就恢復讓它繼續)的等價機制。
如果你有不錯的建議歡迎和我一起討論!
Reference
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/756/viewspace-2825710/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 異常處理機制(二)之異常處理與捕獲
- IOS系統閃退異常(Crash)捕獲處理iOS
- python中如何捕獲異常Python
- 捕獲 React 異常React
- 異常及捕獲
- python異常捕獲Python
- 一對一影片原始碼,非同步中出現了異常該如何處理?原始碼非同步
- Mysql系列第十九講 異常捕獲及處理詳解MySql
- Task異常捕獲的方式
- android 異常捕獲-UncaughtExceptionHandlerAndroidException
- 【求助】如何捕獲 pytest parametrize 中的 timeout 異常
- JSP 異常處理如何處理?JS
- Auth 授權的異常捕獲
- pb呼叫ole異常捕獲
- 記錄Javascript 異常捕獲JavaScript
- wpf 捕獲全域性異常
- 【Spring Cloud】Feign呼叫異常觸發降級後如何捕獲異常SpringCloud
- 如何優雅的處理異常
- SpringBoot之全域性捕獲異常Spring Boot
- 10. 異常捕獲、生成式
- JS 使用try catch捕獲異常JS
- 異常的處理
- 前端JavaScript 常見的報錯及異常捕獲前端JavaScript
- 異常-throws的方式處理異常
- 儲存過程——異常捕獲&列印異常資訊儲存過程
- 異常篇——異常處理
- gRPC 中的異常該如何處理?RPC
- Java培訓簡述如何處理沒有被捕獲的異常Java
- 異常處理
- DRF之異常捕獲原始碼分析原始碼
- 捕獲不到異常嘗試除以0
- 在 C++ 中捕獲 Python 異常C++Python
- spring-boot 統一異常捕獲Springboot
- 如何自定義一個全域性異常捕獲器-SpiderManIDE
- Java捕獲非檢查異常----UncaughtExceptionHandler的使用JavaException
- 前端開發中的Error以及異常捕獲前端Error
- 如何優雅處理前端異常?前端
- 如何使用SpringMvc處理Rest異常SpringMVCREST