譯者按:根據墨菲定律:“有可能出錯的事情,就會出錯”。那么,既然代碼必然會出錯,我們就應該處理好異常。
為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。
處理異常是編程非常重要的一點。我們的程序依賴於第三方服務、數據庫以及我們的用戶,一切都不可預料。數據庫可能會宕機,第三方服務可能會崩潰,用戶可能會使用錯誤的參數調用我們的接口。
為了處理各種復雜的情況,我們必須處理好代碼異常,下面是代碼示例:
app.get('/users/:id', (req, res) => {
const userId = req.params.id
if (!userId) {
return res.sendStatus(400).json({
error: 'Missing id'
})
}
Users.get(userId, (err, user) => {
if (err) {
return res.sendStatus(500).json(err)
}
res.send(users)
})
})
代碼中處理了異常,但是存在問題:
- 在多處代碼處理異常
- 沒有使用Express的異常處理模塊來統一處理異常
接下來,我們來一步步優化代碼異常處理。
Express異常處理中間件
所有Express的路由處理函數都有第三個參數next,它可以用來調用下一個中間件,也可以將錯誤傳遞給錯誤處理中間件:
app.get('/users/:id', (req, res, next) => {
const userId = req.params.id
if (!userId) {
const error = new Error('missing id')
error.httpStatusCode = 400
return next(error)
}
Users.get(userId, (err, user) => {
if (err) {
err.httpStatusCode = 500
return next(err)
}
res.send(users)
})
})
使用next(err),Express就知道出錯了,並把這個錯誤傳遞給錯誤處理模塊。為了處理這些錯誤,需要添加一個中間件,它有4個參數:
app.use((err, req, res, next) => {
// log the error...
res.sendStatus(err.httpStatusCode).json(err)
})
這樣,我們就可以使用中間件統一處理錯誤了。但是,現在的代碼有些重復:創建錯誤,指定HTTP狀態碼,使用next(err)...
Fundebug是全棧JavaScript錯誤監控平台,支持各種前端和后端框架,可以幫助您第一時間發現BUG!
boom
boom是一個兼容HTTP的錯誤對象,他提供了一些標准的HTTP錯誤,比如400(參數錯誤)等。
const boom = require('boom')
app.get('/users/:id', (req, res, next) => {
const userId = req.params.id
if (!userId) {
return next(boom.badRequest('missing id'))
}
Users.get(userId, (err, user) => {
if (err) {
return next(boom.badImplementation(err))
}
res.send(users)
})
})
錯誤處理中間件需要稍作修改:
app.use((err, req, res, next) => {
if (err.isServer) {
// log the error...
// probably you don't want to log unauthorized access
// or do you?
}
return res.status(err.output.statusCode).json(err.output.payload);
})
Async/Await錯誤處理
使用Async/Await之后,可以這樣處理Express異常:
- 將中間件使用Promise封裝起來,使用catch統一處理異常
- 在中間件中,直接拋出異常就可以了
const boom = require('boom');
// wrapper for our async route handlers
// probably you want to move it to a new file
const asyncMiddleware = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch((err) => {
if (!err.isBoom) {
return next(boom.badImplementation(err));
}
next(err);
});
};
// the async route handler
app.get('/users/:id', asyncMiddleware(async (req, res) => {
const userId = req.params.id
if (!userId) {
throw boom.badRequest('missing id')
}
const user = await Users.get(userId)
res.json(user)
}))
參考
版權聲明:
轉載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/12/06/handle-express-error/