錯誤處理是指Express如何捕獲和處理同步和異步發生的錯誤,Express附帶一個默認的錯誤處理程序,所以你無需編寫本身的錯誤處理程序便可開始使用。json
確保Express捕獲運行路由處理程序和中間件時發生的全部錯誤很是重要。segmentfault
路由處理程序和中間件內的同步代碼中發生的錯誤不須要額外的工做,若是同步代碼拋出錯誤,則Express將捕獲並處理它,例如:promise
app.get("/", function (req, res) { throw new Error("BROKEN"); // Express will catch this on its own. });
對於由路由處理程序和中間件調用的異步函數返回的錯誤,必須將它們傳遞給next()
函數,Express將捕獲並處理它們,例如:app
app.get("/", function (req, res, next) { fs.readFile("/file-does-not-exist", function (err, data) { if (err) { next(err); // Pass errors to Express. } else { res.send(data); } }); });
若是將任何內容傳遞給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會捕獲並處理錯誤。ide
你必須捕獲由路由處理程序或中間件調用的異步代碼中發生的錯誤,並將它們傳遞給Express進行處理,例如:函數
app.get("/", function (req, res, next) { setTimeout(function () { try { throw new Error("BROKEN"); } catch (err) { next(err); } }, 100); });
上面的示例使用try...catch
塊來捕獲異步代碼中的錯誤並將它們傳遞給Express,若是省略try...catch
塊,Express將不會捕獲錯誤,由於它不是同步處理程序代碼的一部分。ui
使用promises
能夠避免try...catch
塊的開銷或者使用返回promises的函數,例如:this
app.get("/", function (req, res, next) { Promise.resolve().then(function () { throw new Error("BROKEN"); }).catch(next); // Errors will be passed to Express. });
因爲promises
會自動捕獲同步錯誤和拒絕promises,你能夠簡單地提供next
做爲最終的catch
處理程序,Express將捕獲錯誤,由於catch
處理程序被賦予錯誤做爲第一個參數。
你還可使用處理程序鏈來依賴同步錯誤捕獲,經過將異步代碼減小爲一些簡單的代碼,例如:
app.get("/", [ function (req, res, next) { fs.readFile("/maybe-valid-file", "utf8", function (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_ENV
設置爲production
,以在生產模式下運行應用程序。
若是在開始寫入響應後調用next()
並出現錯誤(例如,若是在將響應流式傳輸到客戶端時遇到錯誤),則Express默認錯誤處理程序將關閉鏈接並使請求失敗。
所以,當你添加自定義錯誤處理程序時,必須在headers已發送到客戶端時委託給默認的Express錯誤處理程序:
function errorHandler (err, req, res, next) { if (res.headersSent) { return next(err) } res.status(500) res.render('error', { error: err }) }
請注意,若是你在你的代碼調用next()
出現錯誤屢次,則會觸發默認錯誤處理程序,即便自定義錯誤處理中間件已就緒也是如此。
以與其餘中間件函數相同的方式定義錯誤處理中間件函數,除了錯誤處理函數有四個參數而不是三個:(err, req, res, next)
,例如:
app.use(function (err, req, res, next) { console.error(err.stack) res.status(500).send('Something broke!') })
你能夠在其餘app.use()
和路由調用以後定義錯誤處理中間件,例如:
var bodyParser = require('body-parser') var methodOverride = require('method-override') app.use(bodyParser.urlencoded({ extended: true })) app.use(bodyParser.json()) app.use(methodOverride()) app.use(function (err, req, res, next) { // logic })
中間件函數內的響應能夠是任何格式,例如HTML錯誤頁面、簡單消息或JSON字符串。
對於組織(和更高級別的框架)目的,你能夠定義多個錯誤處理中間件函數,就像使用常規中間件函數同樣,例如,爲使用XHR和不使用XHR的請求定義錯誤處理程序:
var bodyParser = require('body-parser') var 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) } }
實現「catch-all」的errorHandler
函數,以下所示(例如):
function errorHandler (err, req, res, next) { res.status(500) res.render('error', { error: err }) }
若是你有一個具備多個回調函數的路由處理程序,則可使用route
參數跳轉到下一個路由處理程序,例如:
app.get('/a_route_behind_paywall', function checkIfPaidSubscriber (req, res, next) { if (!req.user.hasPaid) { // continue handling this request next('route') } else{ next(); } }, function getPaidContent (req, res, next) { PaidContent.find(function (err, doc) { if (err) return next(err) res.json(doc) }) })
在此示例中,將跳過getPaidContent
處理程序,但app
中的/a_route_behind_paywall
中的任何剩餘處理程序將繼續執行。
對next()
和next(err)
的調用代表當前處理程序已完成並處於什麼狀態,next(err)
將跳過鏈中的全部剩餘處理程序,除了那些設置爲處理上述錯誤的處理程序。