黑人生命也是命。
支持平等正義倡議.

錯誤處理

錯誤處理是指 Express 如何捕捉和處理同步和非同步發生的錯誤。Express 內建預設錯誤處理程式,因此您無須撰寫自己的程式即可開始使用。

捕捉錯誤

確保 Express 捕捉在執行路由處理程式和中間件時發生的所有錯誤非常重要。

路由處理程式和中間件內同步程式碼發生的錯誤不需要額外工作。如果同步程式碼擲回錯誤,Express 會捕捉並處理它。例如

app.get('/', (req, res) => {
  throw new Error('BROKEN') // Express will catch this on its own.
})

對於路由處理常式和中間件呼叫的非同步函數回傳的錯誤,您必須將它們傳遞給 next() 函數,Express 會捕捉並處理它們。例如

app.get('/', (req, res, next) => {
  fs.readFile('/file-does-not-exist', (err, data) => {
    if (err) {
      next(err) // Pass errors to Express.
    } else {
      res.send(data)
    }
  })
})

從 Express 5 開始,回傳 Promise 的路由處理常式和中間件會在拒絕或擲回錯誤時自動呼叫 next(value)。例如

app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

如果 getUserById 擲回錯誤或拒絕,next 會使用擲回的錯誤或拒絕的值呼叫。如果未提供拒絕的值,next 會使用 Express 路由器提供的預設 Error 物件呼叫。

如果您傳遞任何東西給 next() 函數(除了字串 'route'),Express 會將目前的請求視為錯誤,並會略過任何剩下的非錯誤處理路由和中間件函數。

如果序列中的回呼不提供資料,只提供錯誤,您可以簡化此程式碼如下

app.get('/', [
  function (req, res, next) {
    fs.writeFile('/inaccessible-path', 'data', next)
  },
  function (req, res) {
    res.send('OK')
  }
])

在上述範例中,next 提供為 fs.writeFile 的回呼,會使用或不使用錯誤呼叫。如果沒有錯誤,會執行第二個處理常式,否則 Express 會捕捉並處理錯誤。

您必須捕捉路由處理常式或中間件呼叫的非同步程式碼中發生的錯誤,並將它們傳遞給 Express 處理。例如

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})

上述範例使用 try...catch 區塊捕捉非同步程式碼中的錯誤,並將它們傳遞給 Express。如果省略 try...catch 區塊,Express 就不會捕捉錯誤,因為它不是同步處理常式程式碼的一部分。

使用承諾來避免 try...catch 區塊的負擔,或在使用回傳承諾的函數時。例如

app.get('/', (req, res, next) => {
  Promise.resolve().then(() => {
    throw new Error('BROKEN')
  }).catch(next) // Errors will be passed to Express.
})

由於承諾會自動捕捉同步錯誤和拒絕的承諾,因此您可以簡單地提供 next 作為最後的捕捉處理常式,而 Express 會捕捉錯誤,因為捕捉處理常式會將錯誤作為第一個引數提供。

您也可以使用一系列處理常式來依賴同步錯誤捕捉,方法是將非同步程式碼簡化為一些微不足道的東西。例如

app.get('/', [
  function (req, res, next) {
    fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
      res.locals.data = data
      next(err)
    })
  },
  function (req, res) {
    res.locals.data = res.locals.data.split(',')[1]
    res.send(res.locals.data)
  }
])

上述範例有來自 readFile 呼叫的幾個微不足道的陳述式。如果 readFile 導致錯誤,則它會將錯誤傳遞給 Express,否則您會在鏈中的下一個處理常式中快速回到同步錯誤處理的世界。然後,上述範例會嘗試處理資料。如果這失敗了,則同步錯誤處理常式會捕捉它。如果您在 readFile 回呼函式中執行此處理,則應用程式可能會結束,而 Express 錯誤處理常式不會執行。

無論您使用哪一種方法,如果您要呼叫 Express 錯誤處理常式並讓應用程式存活,您必須確保 Express 收到錯誤。

預設錯誤處理常式

Express 附帶內建錯誤處理常式,它會處理應用程式中可能遇到的任何錯誤。此預設錯誤處理中介軟體函式會新增到中介軟體函式堆疊的結尾。

如果您傳遞錯誤給 next(),而且您沒有在自訂錯誤處理常式中處理它,則它會由內建錯誤處理常式處理;錯誤會連同堆疊追蹤寫入至用戶端。堆疊追蹤不會包含在生產環境中。

設定環境變數 NODE_ENVproduction,以在生產模式中執行應用程式。

當錯誤寫入時,下列資訊會新增至回應

如果你在開始撰寫回應後使用錯誤呼叫 next()(例如,如果你在串流回應給用戶端時遇到錯誤),Express 預設錯誤處理程式會關閉連線並讓要求失敗。

因此,當你新增自訂錯誤處理程式時,必須在標頭已經傳送給用戶端時委派給 Express 預設錯誤處理程式

function errorHandler (err, req, res, next) {
  if (res.headersSent) {
    return next(err)
  }
  res.status(500)
  res.render('error', { error: err })
}

請注意,如果你在程式碼中使用錯誤呼叫 next() 超過一次,即使有自訂錯誤處理中間軟體,預設錯誤處理程式也會觸發。

其他錯誤處理中間軟體可以在 Express 中間軟體 中找到。

撰寫錯誤處理程式

定義錯誤處理中間軟體函數的方式與其他中間軟體函數相同,但錯誤處理函數有四個引數,而不是三個:(err, req, res, next)。例如

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

你最後定義錯誤處理中間軟體,在其他 app.use() 和路由呼叫之後;例如

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
  // logic
})

中間軟體函數中的回應可以採用任何格式,例如 HTML 錯誤頁面、簡單訊息或 JSON 字串。

為了組織(和更高層級架構)的目的,你可以定義多個錯誤處理中間軟體函數,就像你使用一般中間軟體函數一樣。例如,要為使用 XHR 和未使用的要求定義錯誤處理程式

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

在此範例中,通用 logErrors 可能會將要求和錯誤資訊寫入 stderr,例如

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

此外,在此範例中,clientErrorHandler 定義如下;在這種情況下,錯誤會明確傳遞到下一個錯誤。

請注意,當在錯誤處理函數中呼叫「next」時,你負責撰寫(並結束)回應。否則,這些要求將「掛起」,並且沒有資格進行垃圾回收。

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

實作「萬用」errorHandler 函數如下(例如)

function errorHandler (err, req, res, next) {
  res.status(500)
  res.render('error', { error: err })
}

如果你有一個具有多個回呼函數的路由處理程式,你可以使用 route 參數跳到下一個路由處理程式。例如

app.get('/a_route_behind_paywall',
  (req, res, next) => {
    if (!req.user.hasPaid) {
      // continue handling this request
      next('route')
    } else {
      next()
    }
  }, (req, res, next) => {
    PaidContent.find((err, doc) => {
      if (err) return next(err)
      res.json(doc)
    })
  })

在此範例中,getPaidContent 處理程式將會被跳過,但 app/a_route_behind_paywall 的任何剩餘處理程式將會繼續執行。

呼叫 next()next(err) 表示目前的處理程式已完成,以及處於什麼狀態。next(err) 將會跳過鏈中的所有剩餘處理程式,除了那些設定為處理錯誤的處理程式,如上所述。