翻譯&編輯/鶴爺javascript
原文/Marc Harterjava
比起回調函數,使用 Promise 來處理異步錯誤要顯得優雅許多。git
結合 Express 內置的錯誤處理機制和 Promise 極大地下降產生未捕獲錯誤(uncaught exception)的可能性。github
Promise 在ES6中是默認選項。若是使用 Babel 轉譯,它也能夠與 Generators 或者 Async/Await 相結合。express
本文主要闡述如何在 Express 中使用錯誤處理中間件(error-handling middleware)來高效處理異步錯誤。在 Github 上有對應代碼實例可供參考。promise
首先,讓咱們一塊兒瞭解 Express 提供的開箱即用的錯誤處理工具。而後,咱們將探討如何使用 Promise, Generators 以及 ES7 的 async/await 來簡化錯誤處理流程。app
在默認狀況下,Express 會捕獲全部在路由處理函數中的拋出的異常,而後將它傳給下一個錯誤處理中間件:異步
app.get('/', function (req, res) {
throw new Error('oh no!')
})
app.use(function (err, req, res, next) {
console.log(err.message) // 噢!不!
})複製代碼
對於同步執行的代碼,以上的處理已經足夠簡單。然而,當異步程序在執行時拋出異常的狀況,Express 就無能爲力。緣由在於當你的程序開始執行回調函數時,它原來的棧信息已經丟失。async
app.get('/', function (req, res) {
queryDb(function (er, data) {
if (er) throw er
})
})
app.use(function (err, req, res, next) {
// 這裏拿不到錯誤信息
})複製代碼
對於這種狀況,能夠使用 next 函數來將錯誤傳遞給下一個錯誤處理中間件函數
app.get('/', function (req, res, next) {
queryDb(function (err, data) {
if (err) return next(err)
// 處理數據
makeCsv(data, function (err, csv) {
if (err) return next(err)
// 處理 csv
})
})
})
app.use(function (err, req, res, next) {
// 處理錯誤
})複製代碼
使用這種方法雖然一時爽,卻帶來了兩個問題:
你須要顯式地在錯誤處理中間件中分別處理不一樣的異常。
一些隱式異常並無被處理(如嘗試獲取一個對象並不存在的屬性)
在異步執行的程序中使用 Promise 處理任何顯式或隱式的異常狀況,只須要在 Promise 鏈尾加上 .catch(next) 便可。
app.get('/', function (req, res, next) {
// do some sync stuff
queryDb()
.then(function (data) {
// 處理數據
return makeCsv(data)
})
.then(function (csv) {
// 處理 csv
})
.catch(next)
})
app.use(function (err, req, res, next) {
// 處理錯誤
})複製代碼
如今,全部異步和同步程序都將被傳遞到錯誤處理中間件。棒棒的。
雖然 Promise 讓異步錯誤的傳遞變得容易,但這樣的代碼仍然有一些冗長和刻板。這時候 promise generator 就派上了用場。
若是你使用的環境原生支持 Generators,你能夠手動實現如下的功能。不過這裏咱們將借用 Bluebird.coroutine 來講明如何使用 Promise generator 來簡化剛纔的代碼。
儘管接下來的例子使用的是 bluebird ,其它 Promise 庫(如 co)也都支持 Promise generator.
首先,咱們須要使得 Express 路由函數與 Promise generator 兼容:
var Promise = require('bluebird')
function wrap (genFn) { // 1
var cr = Promise.coroutine(genFn) // 2
return function (req, res, next) { // 3
cr(req, res, next).catch(next) // 4
}
}複製代碼
這個函數是一個高階函數,它作了如下幾件事情:(分別與代碼片斷中的註釋對應)
以 Genrator 爲惟一的輸入
讓這個函數懂得如何 yield promise
返回一個普通的 Express 路由函數
當這個函數被執行時,它會使用 coroutine 來 yield promise,捕獲期間發生的異常,而後將其傳遞給 next 函數
藉助這個函數,咱們就能夠這樣構造路由函數:
app.get('/', wrap(function *(req, res) {
var data = yield queryDb()
// 處理數據
var csv = yield makeCsv(data)
// 處理 csv
}))
app.use(function (err, req, res, next) {
// 處理錯誤
})複製代碼
如今,Express 的異步錯誤處理流程的可讀性已經近乎使人滿意,並且你能夠像寫同步執行的代碼同樣去書寫異步執行的代碼,惟一不要忘了的就是 yield promises。
然而這還不是終點,ES7 的 async/await 提議可讓代碼變得更簡潔。
ES7 async/await 的行爲就像 Promise Generator 同樣,只不過它能夠被用到更多的地方(如類方法或者胖箭頭函數)。
爲了在 Express 中使用 async/await,同時優雅地處理異步錯誤,咱們仍然須要一個與上文提到的 wrap 相似的函數:
let wrap = fn => (...args) => fn(...args).catch(args[2])複製代碼
這樣,咱們就能夠按底下這種方式書寫路由函數:
app.get('/', wrap(async function (req, res) {
let data = await queryDb()
// 處理數據
let csv = await makeCsv(data)
// 處理 csv
}))複製代碼
有了對同步和異步錯誤的處理,你能夠用新的方式來開發 Express App。但有兩點須要注意:
app.get('/', wrap(async (req, res) => {
if (!req.params.id) {
throw new BadRequestError('Missing Id')
}
let companyLogo
try {
companyLogo = await getBase64Logo(req.params.id)
} catch (err) {
console.error(err)
companyLogo = genericBase64Logo
}
}))複製代碼
app.use(function (err, req, res, next) {
if (err instanceof BadRequestError) {
res.status(400)
return res.send(err.message)
}
...
})複製代碼
事件發射器(如 steams)仍然會致使未捕獲異常,你須要注意合理地處理這類狀況:
```
原連接地址 馬達數據官方公衆平臺, Strong Loop