說到 Express 的錯誤處理機制,就要先來談談 node.js 的 錯誤優先處理(Error-First),這是一種風格,大致上是長這樣的:
const main = (num, cb) => {
if ( !num ) {
cb('有錯誤!');
} else {
cb(null, {});
}
};
// 這裡的 Callback 即 Error-First Callback
main(0, (err, data) => {
if ( err ) {
throw new Error(err);
} else {
console.log('過關!');
}
});
沒錯,就是把錯誤的參數放在 函式第一個 ,在 node.js 裡面會有許多這樣設計的套件,看似沒什麼大礙卻有很可怕的地雷,當有很多層的 Error-First Callback 的時候,就會產生所謂的 回呼地獄(Callback Hell) ,很難閱讀,維護起來更是要人命
所以在這邊呼籲大家...
如果可以用 Promise 或 async/await 的話,絕對優先使用!
如果可以用 Promise 或 async/await 的話,絕對優先使用!
如果可以用 Promise 或 async/await 的話,絕對優先使用!
在 Express 中拋出錯誤的方式大致上可以分成兩種,會在下方進行示範,並在 app.routing.ts
中進行測試:
透過 throw
進行拋出:
router.get('/error', (req, res, next) => {
throw 'error page.';
});
next()
帶入錯誤訊息,Express 會視為錯誤:
router.get('/error', (req, res, next) => {
next('error page.');
});
在瀏覽器中輸入 http://localhost:3000/error 呈現結果:
這個方法限定用 Promise,不能用於 Error-First Callback
如果今天有一連串的資料要處理,並且用 then chain 的方式處理,大致上會長這樣:
router.get('/data/error', (req, res, next) => {
// Fake API
const getProfile = new Promise((resolve, reject) => {
setTimeout(() => resolve({ name: 'HAO', age: 22 }), 100);
});
const getFriends = new Promise((resolve, reject) => {
setTimeout(() => resolve([]), 120);
});
const errorRequest = new Promise((resolve, reject) => {
setTimeout(() => reject('Oops!'), 2000);
});
getProfile
.then(profile => getFriends)
.then(friends => errorRequest)
.then(() => res.send('GoGoGo!'))
.catch(err => next(err));
});
如果比較講究 (龜毛) 的人就會希望不要看到一堆 next(err)
,那可以怎麼做呢?運用 await/async 的機制可以辦到,先定義一個函式:
const errorHandler = (func: (req: Request, res: Response, next: NextFunction) => Promise<void>) => (req: Request, res: Response, next: NextFunction) => func(req, res, next).catch(next);
並把 middleware 帶入此函式中:
router.get('/data/error/promise', errorHandler(async (req, res, next) => {
// Fake API
const getProfile = new Promise((resolve, reject) => {
setTimeout(() => resolve({ name: 'HAO', age: 22 }), 100);
});
const getFriends = new Promise((resolve, reject) => {
setTimeout(() => resolve([]), 120);
});
const errorRequest = new Promise((resolve, reject) => {
setTimeout(() => reject('Oops!'), 2000);
});
const profile = await getProfile;
const friends = await getFriends;
const none = await errorRequest;
res.send('GoGoGo!');
}));
兩者結果相同,但前者會將錯誤處理直接放在 middleware 裡,而後者是將 middleware 包起來,並執行一個被包裝好的 async function,只要發生錯誤就會自動將錯誤傳至 next()
中。下方為執行結果:
Express 本身有內建錯誤處理器,只要拋出錯誤就會將錯誤資訊帶入其中,這個就是 全域錯誤處理 機制,以一個龐大的系統而言,建議自行設計一套全域錯誤處理機制來格式化錯誤資訊,再送回客戶端。究竟要如何設計呢?只需要在 應用程式層 用中介軟體來解決,這個中介軟體正是前面所提到的 Error-First Callback :
app.use(function(err: any, req: Request, res: Response, next: NextFunction) {
res.status(500).json({ message: err.message || err });
});
現在,打開瀏覽器並進入剛剛設計的 http://localhost:3000/data/error 來測試看看是不是變成 JSON 格式了:
錯誤處理是很重要的一部份,只要用一套完善的處理機制,不論對開發人員或是使用者都會非常友善,所以務必要做好錯誤處理呦!下一篇將會介紹不同網域之間取資源的問題,在 Express 中該如何解決?敬請期待!