全棧項目|小書架|伺服器開發-Koa2 全局異常處理

  • 2019 年 11 月 10 日
  • 筆記

什麼是異常

做開發的基本都知道異常,像Android開發中常見的ANR異常、空指針異常,伺服器開發中經常遇到的異常404,500異常,還有一些其他常見的異常,具體可見HTTP狀態碼

基本上這些異常可以總結為:已知異常未知異常

已知異常就是程式中能夠預想到異常,比如:伺服器介面開發中某個api介面需要5個參數,而用戶傳遞的參數多餘5個或者少於5個,這種錯誤就是已知錯誤。

未知異常就說程式中不能預想到的異常,比如:伺服器介面開發中遇到了空指針而程式中又沒有做相應處理就會拋出HTTP狀態碼為500的這種異常,這種就說未知異常。

為什麼需要全局異常處理

當遇到異常時如果沒有全局異常處理,一般是在相應的程式碼邏輯中添加異常捕捉(try ... catch)或者拋出(throw)處理。

這麼做其實是有弊端的:

  1. 程式程式碼判斷邏輯過長,可讀性查,不方便後期維護。
  2. 程式碼耦合性高,每次出現異常都需要在不同的類、文件下寫異常判斷邏輯。

以上只是列舉的幾個弊端,為了解決以上的問題程式中添加全局異常的處理就很有必要了。

這裡使用的是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   }

諮詢請加微信:輕撩即可。
在這裡插入圖片描述