如何優(yōu)雅地在Node應(yīng)用中進(jìn)行錯(cuò)誤異常處理
不知道你有沒有遇到這樣一種情況,某天你寫的代碼在線上突然發(fā)生錯(cuò)誤,然后你打開控制臺(tái),卻對(duì)著打過包的錯(cuò)誤信息毫無頭緒?又或者說是代碼在node端出現(xiàn)了問題,你查看錯(cuò)誤日志的時(shí)候,卻發(fā)現(xiàn)日志文件中都是雜亂的錯(cuò)誤堆棧信息。
其實(shí)上面這些問題都可以通過在代碼中引入合適的錯(cuò)誤機(jī)制進(jìn)行解決。大部分時(shí)候,由于程序員在開發(fā)過程中更加關(guān)注需求的實(shí)現(xiàn),反而會(huì)忽視一些底層的工作。而錯(cuò)誤處理機(jī)制就相當(dāng)于我們代碼上的最后一道保險(xiǎn),在程序發(fā)生已知或者意外的問題的時(shí)候,可以讓開發(fā)者在第一時(shí)間獲取信息,從而快速定位并解決問題。
常用的錯(cuò)誤處理機(jī)制
首先我們來了解一下目前前端領(lǐng)域到底有哪些錯(cuò)誤處理機(jī)制。
try catch
try...catch這種錯(cuò)誤處理機(jī)制一定是大家最熟悉的,Javascript語言內(nèi)置的錯(cuò)誤處理機(jī)制可以在檢測(cè)到代碼異常的時(shí)候直接進(jìn)行捕獲并處理。
function test() {
try {
throw new Error("error");
} catch(err) {
console.log("some error happened:");
}
}
test()
node原生錯(cuò)誤處理機(jī)制
大多數(shù)Node.js核心API都提供的是利用回調(diào)函數(shù)處理錯(cuò)誤,例如:
const fs = require('fs');
function read() {
fs.readFile("/some/file/does-not-exist", (err, data) => {
if(err) {
throw new Error("file not exist");
}
console.log(data);
});
}
read();
通過回調(diào)函數(shù)的err參數(shù)來檢查是否出現(xiàn)錯(cuò)誤,再進(jìn)行處理。之所以Node.js采用這種錯(cuò)誤處理機(jī)制,是因?yàn)楫惒椒椒ㄋa(chǎn)生的方法并不能簡(jiǎn)單地通過try...catch機(jī)制進(jìn)行攔截。
promise
Promise是用于處理異步調(diào)用的規(guī)范,而其提供的錯(cuò)誤處理機(jī)制,是通過catch方法進(jìn)行捕獲。
fs.mkdir("./temp").then(() => {
fs.writeFile("./temp/foobar.txt", "hello");
}).catch(err => {
console.log(err)
});
async/await + try catch
第三種錯(cuò)誤處理機(jī)制是采用async/await語法糖加上try...catch語句進(jìn)行的。這樣做的好處是異步和同步調(diào)用都能夠使用統(tǒng)一的方式進(jìn)行處理了。
async function one() {
await two();
}
async function two() {
await "hello";
throw new Error("error");
}
async function test() {
try {
await one();
} catch(error) {
console.log(error);
}
}
test();
解決方案
promisify
如果你的代碼中充斥著多種不同的錯(cuò)誤處理模式,那么維護(hù)起來是十分困難的。而且代碼的可讀性也會(huì)大大降低。因此,這里推薦采用的統(tǒng)一的解決方案。對(duì)于同步代碼來說,直接使用try...catch方式進(jìn)行捕獲處理即可,而對(duì)于異步代碼來說,建議轉(zhuǎn)換成Promise然后采用async/await + try...catch這種方式進(jìn)行處理。這樣風(fēng)格統(tǒng)一,程序的健壯性也大大加強(qiáng)。例如下面這個(gè)數(shù)據(jù)庫請(qǐng)求的代碼:
const database = require("database");
function promiseGet(query) {
return new Promise((resolve, reject) => {
database.get(query, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
})
})
}
async function main() {
await promiseGet("foo");
}
main();
自定義錯(cuò)誤類型
直接使用系統(tǒng)原生的錯(cuò)誤信息通常會(huì)顯得太過單薄,不利于后續(xù)進(jìn)一步的分析和處理。所以為了讓代碼的錯(cuò)誤處理機(jī)制的功能更加強(qiáng)大,我們勢(shì)必要多花點(diǎn)精力進(jìn)行額外的改造。
可以通過擴(kuò)展基礎(chǔ)的Error類型來達(dá)到這一目的。
一般來說,要根據(jù)錯(cuò)誤發(fā)生的位置采用不同的錯(cuò)誤類型。
首先是應(yīng)用層錯(cuò)誤,它會(huì)保存額外的線索數(shù)據(jù):
class ApplicationError extends Error {
constructor(message, options = {}) {
assert(typeof message === 'string');
assert(typeof options === 'object');
assert(options !== null);
super(message);
// Attach relevant information to the error instance
// (e.g., the username).
for (const [key, value] of Object.entries(options)) {
this[key] = value;
}
}
get name() {
return this.constructor.name;
}
}
接著,可以再定義用戶接口的錯(cuò)誤類型,該類型主要用于直接返回給客戶端,比如錯(cuò)誤狀態(tài)碼等等。
class UserFacingError extends ApplicationError {
constructor(message, options = {}) {
super(message, options);
}
}
class BadRequestError extends UserFacingError {
get statusCode() {
return 400
}
}
class NotFoundError extends UserFacingError {
get statusCode() {
return 404
}
}
另外,對(duì)于底層的數(shù)據(jù)庫錯(cuò)誤來說,可能需要更加細(xì)節(jié)的錯(cuò)誤信息。此時(shí)也可以根據(jù)業(yè)務(wù)需要進(jìn)行自定義:
class DatabaseError extends ApplicationError {
get toString() {
return "Errored happend in query: " + this.query + "\n" + this.message;
}
}
// 使用的話
throw new DatabaseError("Other message", {
query: query
});
化繁為簡(jiǎn),集中處理
express
有了基礎(chǔ)的錯(cuò)誤數(shù)據(jù)類型后,我們可以在代碼里針對(duì)不同的錯(cuò)誤類型采取不同的解決方案。 接下來,以Express應(yīng)用為例講解一下使用方法。
app.use('/user', (req, res, next) => {
const data = await database.getData(req.params.userId);
if (!data) {
throw new NotFoundError("User not found")
}
// do other thing
});
// 錯(cuò)誤處理中間件
app.use(async (err, req, res, next) => {
if (err instanceof UserFacingError) {
res.sendStatus(err.statusCode);
// or
res.status(err.statusCode).send(err.errorCode)
} else {
res.sendStatus(500)
}
// 記錄日志
await logger.logError(err, 'parameter:', req.params, 'User Data:', req.user);
// 發(fā)送郵件
await sendMailToAdminIfCritical();
})
具體到實(shí)際場(chǎng)景中,需要在不同的路由中拋出不同的錯(cuò)誤類型,然后我們就可以通過在錯(cuò)誤處理中間件中進(jìn)行統(tǒng)一的處理。比如根據(jù)不同的錯(cuò)誤類型返回不同的錯(cuò)誤碼。還可以進(jìn)行記錄日志,發(fā)送郵件等操作。
database
數(shù)據(jù)庫發(fā)生錯(cuò)誤的時(shí)候,除了常規(guī)的拋出錯(cuò)誤,有時(shí)候你可能還需要進(jìn)行額外的重試或回退操作,如:
// 發(fā)生網(wǎng)絡(luò)錯(cuò)誤的時(shí)候隔200ms,重試3次
function query(queryStr, token, repeatTime = 0, delay = 200) {
try {
await db.query(queryStr);
} catch (err) {
if (err instanceof NetworkError && repeatTime < 3) {
query(queryStr, token, repeatTime + 1, delay);
}
throw err;
}
}
未處理錯(cuò)誤
對(duì)于未處理的錯(cuò)誤來說,我們可以使用node.js的unhandledRejection事件進(jìn)行監(jiān)聽:
process.on('unhandledRejection', error => {
console.error('unhandledRejection', error);
// To exit with a 'failure' code
process.exit(1);
});
而且從Node.js 12.0開始,可以使用以下命令啟動(dòng)程序:
node app.js --unhandled-rejections
這樣也能夠在發(fā)現(xiàn)未處理異常的時(shí)候進(jìn)行處理,官方支持了三種對(duì)應(yīng)的處理模式:
strict: Raise the unhandled rejection as an uncaught exception.
warn: Always trigger a warning, no matter if the unhandledRejection hook is set or not but do not print the deprecation warning.
none: Silence all warnings.
總結(jié)
最后,總結(jié)一下。為了實(shí)現(xiàn)可擴(kuò)展和可維護(hù)的錯(cuò)誤處理機(jī)制,我們可以需要注意以下幾個(gè)方面:
- 使用自定義Error類,后續(xù)還能根據(jù)業(yè)務(wù)需要進(jìn)行擴(kuò)展
- 將異步代碼轉(zhuǎn)換成Promise,然后統(tǒng)一使用async/await + try...catch的形式進(jìn)行錯(cuò)誤捕獲
- 盡量采用統(tǒng)一的錯(cuò)誤處理中間件函數(shù)
- 保持Error信息可理解,返回合適的錯(cuò)誤狀態(tài)和代碼
- 對(duì)于未處理的錯(cuò)誤,要即使捕獲并記錄
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解webpack打包nodejs項(xiàng)目(前端代碼)
這篇文章主要介紹了webpack打包nodejs項(xiàng)目(前端代碼),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09
Node.js實(shí)現(xiàn)在目錄中查找某個(gè)字符串及所在文件
這篇文章主要介紹了Node.js實(shí)現(xiàn)在目錄中查找某個(gè)字符串及所在文件,文中代碼簡(jiǎn)潔,而且速度相當(dāng)?shù)目?需要的朋友可以參考下2014-09-09
使用?Node.js和Express搭建服務(wù)器的過程步驟詳解
Node.js?是一個(gè)開源、跨平臺(tái)的?JavaScript?運(yùn)行時(shí)環(huán)境,這篇文章主要介紹了如何使用?Node.js和Express搭建服務(wù)器,需要的朋友可以參考下2023-09-09
nodejs和npm版本不匹配報(bào)錯(cuò)的解決方法
當(dāng)公司要求使用固定nodejs的版本時(shí),自己不小心更新了npm,就會(huì)導(dǎo)致npm和nodejs不匹配,下面這篇文章主要給大家介紹了關(guān)于nodejs和npm版本不匹配報(bào)錯(cuò)的解決方法,需要的朋友可以參考下2023-04-04
nodejs同步調(diào)用獲取mysql數(shù)據(jù)時(shí)遇到的大坑
今天小編就為大家分享一篇關(guān)于nodejs同步調(diào)用獲取mysql數(shù)據(jù)時(shí)遇到的大坑,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
Node.js基礎(chǔ)入門之path模塊,url模塊,http模塊使用詳解
這篇文章主要為大家介紹了Node.js中的三個(gè)模塊(path、url、http)的使用詳解,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-03-03
Node.js?中的服務(wù)器相關(guān)概念(常見的服務(wù)器任務(wù))
Node.js?中,服務(wù)器的創(chuàng)建與管理是非常重要的內(nèi)容,Node.js?的非阻塞?I/O?特性使得它特別適合處理高并發(fā)的請(qǐng)求,本文將介紹一些與?Node.js?服務(wù)器相關(guān)的基本概念,包括?HTTP?服務(wù)器、請(qǐng)求和響應(yīng)、路由、以及如何處理常見的服務(wù)器任務(wù),感興趣的朋友一起看看吧2025-04-04
Node.js fs模塊(文件模塊)創(chuàng)建、刪除目錄(文件)讀取寫入文件流的方法
這篇文章主要介紹了Node.js fs模塊(文件模塊)創(chuàng)建、刪除目錄(文件)讀取寫入文件流的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09

