如何處理Express異常?


譯者按:根據墨菲定律:“有可能出錯的事情,就會出錯”。那么,既然代碼必然會出錯,我們就應該處理好異常。

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。

處理異常是編程非常重要的一點。我們的程序依賴於第三方服務、數據庫以及我們的用戶,一切都不可預料。數據庫可能會宕機,第三方服務可能會崩潰,用戶可能會使用錯誤的參數調用我們的接口。

為了處理各種復雜的情況,我們必須處理好代碼異常,下面是代碼示例:

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)
}))

參考

  • 驗證HTTP請求參數可以使用joi模塊
  • 打印日志可以使用winston或者pino模塊

版權聲明:
轉載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/12/06/handle-express-error/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM