不知道你有沒有遇到這樣一種狀況,某天你寫的代碼在線上忽然發生錯誤,而後你打開控制檯,卻對着打過包的錯誤信息毫無頭緒?又或者說是代碼在node端出現了問題,你查看錯誤日誌的時候,卻發現日誌文件中都是雜亂的錯誤堆棧信息。javascript
其實上面這些問題均可以經過在代碼中引入合適的錯誤機制進行解決。大部分時候,因爲程序員在開發過程當中更加關注需求的實現,反而會忽視一些底層的工做。而錯誤處理機制就至關於咱們代碼上的最後一道保險,在程序發生已知或者意外的問題的時候,可讓開發者在第一時間獲取信息,從而快速定位並解決問題。前端
首先咱們來了解一下目前前端領域到底有哪些錯誤處理機制。java
try...catch這種錯誤處理機制必定是你們最熟悉的,Javascript語言內置的錯誤處理機制能夠在檢測到代碼異常的時候直接進行捕獲並處理。node
function test() {
try {
throw new Error("error");
} catch(err) {
console.log("some error happened:");
}
}
test()
複製代碼
大多數Node.js核心API都提供的是利用回調函數處理錯誤,例如:git
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();
複製代碼
經過回調函數的err參數來檢查是否出現錯誤,再進行處理。之因此Node.js採用這種錯誤處理機制,是由於異步方法所產生的方法並不能簡單地經過try...catch
機制進行攔截。程序員
Promise是用於處理異步調用的規範,而其提供的錯誤處理機制,是經過catch
方法進行捕獲。github
fs.mkdir("./temp").then(() => {
fs.writeFile("./temp/foobar.txt", "hello");
}).catch(err => {
console.log(err)
});
複製代碼
第三種錯誤處理機制是採用async/await語法糖加上try...catch
語句進行的。這樣作的好處是異步和同步調用都可以使用統一的方式進行處理了。typescript
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();
複製代碼
若是你的代碼中充斥着多種不一樣的錯誤處理模式,那麼維護起來是十分困難的。並且代碼的可讀性也會大大下降。所以,這裏推薦採用的統一的解決方案。對於同步代碼來講,直接使用try...catch
方式進行捕獲處理便可,而對於異步代碼來講,建議轉換成Promise而後採用async/await + try...catch
這種方式進行處理。這樣風格統一,程序的健壯性也大大增強。例以下面這個數據庫請求的代碼:數據庫
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();
複製代碼
直接使用系統原生的錯誤信息一般會顯得太過單薄,不利於後續進一步的分析和處理。因此爲了讓代碼的錯誤處理機制的功能更增強大,咱們勢必要多花點精力進行額外的改造。express
能夠經過擴展基礎的Error類型來達到這一目的。
通常來講,要根據錯誤發生的位置採用不一樣的錯誤類型。
首先是應用層錯誤,它會保存額外的線索數據:
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;
}
}
複製代碼
接着,能夠再定義用戶接口的錯誤類型,該類型主要用於直接返回給客戶端,好比錯誤狀態碼等等。
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
}
}
複製代碼
另外,對於底層的數據庫錯誤來講,可能須要更加細節的錯誤信息。此時也能夠根據業務須要進行自定義:
class DatabaseError extends ApplicationError {
get toString() {
return "Errored happend in query: " + this.query + "\n" + this.message;
}
}
// 使用的話
throw new DatabaseError("Other message", {
query: query
});
複製代碼
有了基礎的錯誤數據類型後,咱們能夠在代碼裏針對不一樣的錯誤類型採起不一樣的解決方案。 接下來,以Express應用爲例講解一下使用方法。
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
});
// 錯誤處理中間件
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);
// 發送郵件
await sendMailToAdminIfCritical();
})
複製代碼
具體到實際場景中,須要在不一樣的路由中拋出不一樣的錯誤類型,而後咱們就能夠經過在錯誤處理中間件中進行統一的處理。好比根據不一樣的錯誤類型返回不一樣的錯誤碼。還能夠進行記錄日誌,發送郵件等操做。
數據庫發生錯誤的時候,除了常規的拋出錯誤,有時候你可能還須要進行額外的重試或回退操做,如:
// 發生網絡錯誤的時候隔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;
}
}
複製代碼
對於未處理的錯誤來講,咱們可使用node.js的unhandledRejection
事件進行監聽:
process.on('unhandledRejection', error => {
console.error('unhandledRejection', error);
// To exit with a 'failure' code
process.exit(1);
});
複製代碼
並且從Node.js 12.0開始,可使用如下命令啓動程序:
node app.js --unhandled-rejections
複製代碼
這樣也可以在發現未處理異常的時候進行處理,官方支持了三種對應的處理模式:
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.
最後,總結一下。爲了實現可擴展和可維護的錯誤處理機制,咱們能夠須要注意如下幾個方面:
async/await + try...catch
的形式進行錯誤捕獲——轉載請註明出處———
微信掃描二維碼,關注個人公衆號
最後,歡迎你們關注個人公衆號,一塊兒學習交流。
youtu.be/ArfAzp_bSq4 softwareontheroad.com/error-handl… github.com/goldbergyon… michalzalecki.com/an-elegant-… khalilstemmler.com/articles/en…