什么是異常
做開發的基本都知道異常
,像Android
開發中常見的ANR
異常、空指針
異常,服務器開發中經常遇到的異常404,500
異常,還有一些其他常見的異常,具體可見HTTP狀態碼。
基本上這些異常可以總結為:已知異常
和未知異常
。
已知異常就是程序中能夠預想到異常,比如:服務器接口開發中某個api
接口需要5
個參數,而用戶傳遞的參數多余5個
或者少於5個
,這種錯誤就是已知錯誤。
未知異常就說程序中不能預想到的異常,比如:服務器接口開發中遇到了空指針而程序中又沒有做相應處理就會拋出HTTP
狀態碼為500
的這種異常,這種就說未知異常。
為什么需要全局異常處理
當遇到異常時如果沒有全局異常處理,一般是在相應的代碼邏輯中添加異常捕捉(try ... catch)
或者拋出(throw)
處理。
這么做其實是有弊端的:
- 程序代碼判斷邏輯過長,可讀性查,不方便后期維護。
- 代碼耦合性高,每次出現異常都需要在不同的
類、文件
下寫異常判斷邏輯。
以上只是列舉的幾個弊端,為了解決以上的問題程序中添加全局異常的處理就很有必要了。
這里使用的是NodeJS+Koa2
開發。在Koa
中,中間件是無處不在,所以這里全局異常的處理也是通過中間件的方式去實現。
如何處理
1、明確是否需要拋出異常
在服務器接口開發中需要明確是生產環境
還是開發環境
。
在生產環境
中如果出現異常需要將詳細的異常信息上報同時將異常狀態通過api
返回給客戶端處理
在開發環境
中如果出現異常則需要將詳細的異常信息在開發工具的控制台顯示,同時返回將異常狀態通過api
返回給客戶端處理。
這里的區別就說生產環境
和開發環境
,所以通過定義一個全局變量去判斷即可。由於程序中全局變量可能不止一個,為了統一聲明全局變量,我們將所有的全局變量放在一個文件中,統一去加載。
新建一個config.js
,里面的environment:'dev'
就是對環境聲明的變量,這里dev
表示開發環境
,prod
表示生產環境
。
module.exports = {
// prod 表示生產環境
environment:'dev',
database:{
dbName:'book',
host:'localhost',
port:3306,
user:'用戶名',
password:'密碼',
},
// token 設置為1天
security:{
secretKey:"密鑰,要記住不能弄丟了哦",
expiresIn:60*60*24
},
host:'http://localhost:3000/'
}
配置了全局變量之后,在init.js
文件的InitManager
類中定義靜態方法:
/**
* 加載全局配置文件
* @param {''} path 當前路徑
*/
static loadConfig(path = '') {
const configPath = path || process.cwd() + '/config/config.js'
const config = require(configPath)
global.config = config
}
/**
* 加載全局異常
*/
static loadHttpException(){
const errors = require('./http-exception')
global.errs = errors
}
最后在app.js
中完成初始化。
const app = new Koa()
InitManager.loadConfig()
InitManager.loadHttpException()
定義全局變量之后就需要制定返回的api
異常描述
2、定義異常的返回結果
在服務器接口開發中,一個異常的返回結果,通常包含有:
msg
:異常描述errorcode
:自定義的異常狀態碼code
:網絡請求的狀態碼
兩個code
的區別:
errorcode
:自定義的錯誤碼,配合code
定位具體的異常。code
:網絡請求的狀態碼,如403 權限受限
,而權限受限的原因有很多種,比如未登錄或者登錄了但是權限不足,這時候可以結合自定義的錯誤碼和異常描述准確明確告知用戶出錯的原因。
定義異常描述之后就需要去判斷程序是已知異常還是未知異常。
3、明確是已知異常還是未知異常
已知異常:可以定義HttpException
繼承Error
這個類,只要是出現這異常屬於HttpException
都屬於已知異常。
http-exception.js
/**
* 默認的異常
*/
class HttpException extends Error{
constructor(msg='服務器異常',errorCode=10000, code=400){
super()
this.errorCode = errorCode
this.code = code
this.msg = msg
}
}
/**
* 資源未找到提示
*/
class NotFound extends HttpException{
constructor(msg, errorCode) {
super()
this.msg = msg || '資源未找到'
this.errorCode = errorCode || 10000
this.code = 404
}
}
module.exports = {
HttpException,
NotFound
}
環境變量聲明了、異常也做了處理,那么如何監聽全局異常呢?
4、全局異常監聽
首先編寫捕捉異常處理中間件catchError.js
const {HttpException} = require('../core/http-exception')
const catchError = async (ctx, next)=>{
try {
await next()
} catch (error) {
// 已知異常
const isHttpException = error instanceof HttpException
// 開發環境
const isDev = global.config.environment === 'dev'
// 在控制台顯示未知異常信息:開發環境 不是HttpException 拋出異常
if(isDev && !isHttpException){
throw error
}
/**
* 是已知錯誤,還是未知錯誤
* 返回:
* msg 錯誤信息
* error_code 錯誤碼
* request 請求的接口路徑
*/
if(isHttpException){
ctx.body = {
msg:error.msg,
error_code:error.errorCode,
request:`${ctx.method} ${ctx.path}`
}
ctx.status = error.code
}
else{
ctx.body = {
msg: '服務器出現了未知異常',
error_code: 999,
request:`${ctx.method} ${ctx.path}`
}
ctx.status = 500
}
}
}
module.exports = catchError
然后在app.js
中加載中間件
const catchError = require('./middlewares/exception')
const app = new Koa()
// 全局異常中間件監聽、處理,放在所有中間件的最前面
app.use(catchError)
app.listen(3000)
以上就完成了全局異常的處理,接下來就是如何使用這個全局異常了。
5、出現異常后及時拋出異常
這里以資源未找到
為例。在查詢數據庫中,有的時候會出現數據找不到情況,這是用就可以拋出資源未找到
的異常。
在models/book.js
/**
* 獲取書籍詳情
* @param {書籍id} bid
*/
static async detail(bkid) {
const book =await Book.findOne({
where: {
bkid
}
})
if (!book) {
// 通過全局異常的方式,拋出資源未找到的錯誤
throw new global.errs.NotFound()
}
return book
}
咨詢請加微信:輕撩即可。