Koa異常處理說明
作者:zjruan 日期:2017/07/10
使用篇:
Demo:
// mc/homework
// 個(gè)人中心-我的課程-課后作業(yè)
get_homework() {
// 你的處理邏輯 start
...
let lessonId = this.query.lessonid;
let resBody = yield courseMemberCenterApi.getHomework(lessonId);
...
yield this.render('page/mc/homework-index', { title: '課后作業(yè)', leftNavIndex: 'myCourse', exampaper: exampaper })
// 你的處理邏輯 end
}
一哮伟、Controller 中 try catch 的使用
由于項(xiàng)目在Koa頂層有統(tǒng)一的異常處理,因此正常情況下,我們是需要編寫 try catch
的耕赘,除非我們需要做對(duì)這個(gè)異常進(jìn)行編輯辩棒,或者是這個(gè)異常我們自己能處理,不希望該異常冒泡到統(tǒng)一異常處理函數(shù)羡玛。
// 1别智、對(duì)異常進(jìn)行修改,并拋出修改后的異常
get_homework() {
try{
// 你的處理邏輯
......
}catch(error){
error.message = '這個(gè)異常我知道稼稿,是后端數(shù)據(jù)庫中的臟數(shù)據(jù)引起的薄榛!';
throw error; // 拋出處理后的異常
}
}
// 2、已知異常让歼,自己處理而不希望拋出到上層
get_homework() {
try{
// 你的處理邏輯
......
}catch(error){
error.message = '這個(gè)異常我知道敞恋,是后端數(shù)據(jù)庫中的臟數(shù)據(jù)引起的!';
// console.log('我知道這么處理谋右,而且我不想讓別人知道');
// this.redirct('login') // 異常處理邏輯
}
}
二硬猫、如何生成自己的異常
異常在各種開發(fā)語言中都有廣泛的應(yīng)用,適時(shí)的拋出有效的異掣闹矗可以幫助開發(fā)者理解程序邏輯浦徊。例如:
get_homework() {
// 你的處理邏輯 start
...
let lessonId = this.query.lessonid;
// 由于后續(xù)邏輯嚴(yán)重依賴 lessonid,當(dāng) lessonid 無效時(shí),后續(xù)操作便沒有意義天梧,而且耽誤時(shí)間盔性。
// 因此對(duì)依賴參數(shù)進(jìn)行有效性檢查是必要的
if(!lessonId){
this.throw('400', 'lessonid 無效');
}
let resBody = yield courseMemberCenterApi.getHomework(lessonId);
...
yield this.render('page/mc/homework-index', { title: '課后作業(yè)', leftNavIndex: 'myCourse', exampaper: exampaper })
// 你的處理邏輯 end
}
拋出異常語法:
// ctx 環(huán)境變量
// statusCode http錯(cuò)誤碼,錯(cuò)誤碼是有限制的呢岗,無效的錯(cuò)誤碼會(huì)被替換為500冕香,錯(cuò)誤碼請(qǐng)看附錄
// message 異常描述蛹尝,可選
// param 異常攜帶的參數(shù), 可選
ctx.throw(statusCode [, message], [param])
koa uses http-errors to create errors悉尾,點(diǎn)過去看看突那。
Koa 統(tǒng)一異常處理
統(tǒng)一異常處理的好處不用多說,那么在 Koa 中构眯,如何使用愕难?
我們先查看 Koa 的 API 文檔,找到錯(cuò)誤處理段落惫霸,原文如下:
Error Handling
By default outputs all errors to stderr unless app.silent is true. The default error handler also won't outputs errors when err.status is 404 or err.expose is true. To perform custom error-handling logic such as centralized logging you can add an "error" event listener:
app.on('error', err => log.error('server error', err) );
If an error is in the req/res cycle and it is not possible to respond to the client, the Context instance is also passed:
app.on('error', (err, ctx) => log.error('server error', err, ctx) );
When an error occurs and it is still possible to respond to the client, aka no data has been written to the socket, Koa will respond appropriately with a 500 "Internal Server Error". In either case an app-level "error" is emitted for logging purposes.
因此猫缭,我們只需要給 Koa 添加一個(gè)異常監(jiān)聽事件并且處理這個(gè)事件就可以了。
// 注意:以下代碼為偽代碼壹店,無法直接復(fù)制運(yùn)行
// app.js
import errorHandler from 'middleware/errorHandler.js'
...
app.on(error, errorHandler);
// errorHandler.js
module.exports = function(err, ctx) {
// 未知異常狀態(tài)猜丹,默認(rèn)使用 500
if(!err.status) err.status = 500;
ctx.status = err.status;
// 獲取客戶端請(qǐng)求接受類型
let acceptedType = ctx.accepts('html', 'text', 'json');
switch(acceptedType){
case 'text':
ctx.type = 'text/plain';
ctx.body = err.message;
break;
case 'json':
ctx.type = 'application/json';
ctx.body = {error: err.message}
break;
case 'html':
default:
// 默認(rèn)返回頁面
ctx.type = 'text/html';
ctx.redirect(getUrl(err.status));
break;
}
/**
* 根據(jù) Http 狀態(tài)碼,獲取重定向頁面
*
* param {number} status http狀態(tài)碼
*/
function getUrl(status){
switch(error.status){
case 401: url = '401.html'; break;
case 404: url = '404.html'; break;
case 500: url = '500.html'; break;
case 502: url = '502.html'; break;
default:
if(err.status < 500) {
url = '40x.html';
} else {
url = '50x.html';
ctx.redirect('50x.html'))
}
}
return url;
}
}
到了這一步應(yīng)該就差不多了硅卢,思路是添加一個(gè)全局的異常監(jiān)聽事件射窒,當(dāng)異常發(fā)生時(shí),對(duì)其進(jìn)行統(tǒng)一的處理将塑。
根據(jù)客戶端接受數(shù)據(jù)類型脉顿,區(qū)分返回格式,對(duì)于請(qǐng)求頁面的点寥,我們最好能重定向到相應(yīng)的頁面艾疟,區(qū)分 404 和 500 等異常頁面,對(duì)用戶來說會(huì)更加友好绒窑。
由于異常頁面數(shù)量有限镐牺,直接寫成靜態(tài)頁面即可,雖然節(jié)約的性能并不明顯,主要是 UI 可能會(huì)提供不同風(fēng)格的異常頁面闪盔,方便定制。
404 頁面
所謂的統(tǒng)一異常處理售躁,無非就是在程序的最外層价匠,包上一層try catch
,以至于所有的異常都會(huì)被這個(gè)catch
給catch住嘀略。但是我們注意上面的那段文檔中有一句話:
default error handler also won't outputs errors when err.status is 404 or err.expose is true恤溶。
大致意思是 404 是不會(huì)拋出異常的,也就是說我們寫的這個(gè) errorHandler 不會(huì)處理 404頁面帜羊。
那怎么辦咒程?少里 404 的異常處理還叫統(tǒng)一的異常處理么?
顯然不是讼育,所以我們得想辦法解決帐姻。那為什么404不拋出異常呢稠集?因?yàn)?404 是服務(wù)器沒有找到訪問目標(biāo),也就是 Koa 路由沒有匹配到對(duì)于的url饥瓷,并不是執(zhí)行異常剥纷,因此無法被 try catch
catch住。
因?yàn)樗皇钱惓D孛圆荒鼙籧atch住晦鞋,那么我們就為的給它拋出異常,這樣問題不就解決了棺克。
我們需要在 koa-router 加載之前悠垛,添加一個(gè)中間件,如下:
app.use(function* (next) {
yield* next;
if (this.response.status === 404 && !this.response.body) this.throw(404);
});
這樣相當(dāng)于在 koa-router 后面添加了一層判斷逆航。當(dāng)路由匹配失敗后鼎文,判斷返回是不是404,如果是的因俐,我們就手工拋出 404異常, 這樣就能被我們的統(tǒng)一異常處理方案處理了拇惋。
附錄:
Status Code | Constructor Name |
---|---|
400 | BadRequest |
401 | Unauthorized |
402 | PaymentRequired |
403 | Forbidden |
404 | NotFound |
405 | MethodNotAllowed |
406 | NotAcceptable |
407 | ProxyAuthenticationRequired |
408 | RequestTimeout |
409 | Conflict |
410 | Gone |
411 | LengthRequired |
412 | PreconditionFailed |
413 | PayloadTooLarge |
414 | URITooLong |
415 | UnsupportedMediaType |
416 | RangeNotSatisfiable |
417 | ExpectationFailed |
418 | ImATeapot |
421 | MisdirectedRequest |
422 | UnprocessableEntity |
423 | Locked |
424 | FailedDependency |
425 | UnorderedCollection |
426 | UpgradeRequired |
428 | PreconditionRequired |
429 | TooManyRequests |
431 | RequestHeaderFieldsTooLarge |
451 | UnavailableForLegalReasons |
500 | InternalServerError |
501 | NotImplemented |
502 | BadGateway |
503 | ServiceUnavailable |
504 | GatewayTimeout |
505 | HTTPVersionNotSupported |
506 | VariantAlsoNegotiates |
507 | InsufficientStorage |
508 | LoopDetected |
509 | BandwidthLimitExceeded |
510 | NotExtended |
511 | NetworkAuthenticationRequired |