JS異常處理

最近node寫的比較多,後臺應用你懂的,一個異常沒處理好,分分鐘crash給你看。在開發過程當中總結了一些經驗,分享給你們node

Error類

Error類是JS的原生類,在平常開發中也很常見,也很簡單,我在寫文檔以前去MDN上查了下資料:api

Error類的用法很簡單,new或者直接把Error當成function來用都行,而後在你認爲須要拋出異常的地方throw它。promise

JS中有幾種內置的Error類型,好比最多見的ReferrenceError,都繼承自Error,所以咱們本身也能夠定義本身的錯誤類型,只須要繼承Error便可,直接上MDN的栗子:安全

class CustomError extends Error {
  constructor(foo = 'bar', ...params) {
    // Pass remaining arguments (including vendor specific ones) to parent constructor
    super(...params);

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, CustomError);
    }

    // Custom debugging information
    this.foo = foo;
    this.date = new Date();
  }
}

try {
  throw new CustomError('baz', 'bazMessage');
} catch(e){
  console.log(e.foo); //baz
  console.log(e.message); //bazMessage
  console.log(e.stack); //stacktrace
}
複製代碼

如何捕獲異常

try...catch就很少說了,這裏須要提一下Promiseawait的捕獲方式async

Promise裏咱們通常在最後加一個.catch,用來處理整個Promise執行鏈路中任何可能出現的異常,好比:函數

Promise.resolve()
	.then(() => {
		console.log(a); // 這裏會出現異常
	})
	.then(() => {
		console.log('hi'); // 這裏不會執行
	})
	.catch(err => {
		console.log(err); // ReferenceError
	});
複製代碼

await語法返回的也是Promise對象,不過你能夠經過try...catch語法來接住異常post

async function sayHi() {
	try {
		let ret = await anotherPromiseFunction();
	}
	catch (err) {
		console.log(err); // anotherPromiseFunction拋出的異常在這裏處理
	}
}
複製代碼

如何優雅的拋出異常

  1. 你須要一個自定義錯誤類。JS原生的錯誤類型只能定義基本的語言類異常,而咱們在業務代碼中,須要頻繁地定義、拋出一些與業務強相關的異常,好比:

校驗驗證碼的api,驗證碼格式不對時須要拋出一個異常,這個異常應該是跟校驗相關的,且調用者能清晰解讀而且可以根據錯誤信息作出相應處理的。優化

個人自定義錯誤類:ui

/** * @file 錯誤類型彙總 * @author arlenyang */
class ApiError extends Error {
    /** * @constructor * @param {string} code 錯誤碼 * @param {string} msg 中文描述 */
    constructor(code, msg) {
        super(msg);

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, CustomError);
        }

        this.code = code;
        this.msg = msg;
    }

    toString() {
        return `Api${this.stack}\n ${this.msg}, errCode: ${this.code}`;
    }
}

// 錯誤類型
ApiError.MYSQL_QUERY_ERROR = 1;
ApiError.MYSQL_QUERY_ERROR_DESC = '查詢數據失敗';

// ......

複製代碼

構造函數有兩個參數,codemessagethis

  • code是錯誤碼,定義一個異常的簡寫,方便調用者判斷錯誤類型,從而處理錯誤。這在node裏很常見
  • message是錯誤的描述,原生的Error類的構造函數自己就支持

在這個類裏,我用靜態變量的形式存放全部的錯誤碼和它的描述字段,其實也能夠放在一個單獨的存放靜態變量的文件裏

你還能夠擴展你的異常類作更多相關的事情,好比記錄錯誤日誌,上報或者寫入本地日誌。

另外,你還能夠自定義異常的輸出,經過重寫toString方法。還記得以前提到過的error.stackError.captureStackTrace嗎?你能夠在toString方法裏優化異常的輸出格式,加入額外的信息,等等

  1. 異常不宜過分處理。若是寫每個api或函數都去考慮全部可能拋出異常的情形,咱們應該早就累死了~ 咱們須要肯定哪些異常是能夠拋給調用者處理的,這些異常一般是函數執行過程當中可預見的異常(checked exception)。而其餘異常,多是咱們的代碼自己有bug,也多是系統調用產生的error,這類異常須要調用者本身考慮了

舉個栗子,寫一個讀取文件內容的api。

/** * 讀取文件 * @param {String} filepath 文件路徑 * @return {Buffer} 文件內容 */
async function readFile(filepath) {
	
}
複製代碼

根據這個api的行爲能夠預見幾個異常:

  • 入參(filepath)爲空
  • 文件路徑對應的文件不存在
  • 文件路徑對應的文件是否爲文件類型

至於可能出現調用系統讀取文件的api出現的異常filepath不符合文件路徑格式等等的問題,都不是這個api應該考慮的範圍。實現以下:

/** * 讀取文件 * @param {String} filepath 文件路徑 * @return {Buffer} 文件內容 */
async function readFile(filepath) {
	// 檢查filepath是否爲空
	if (!filepath) {
		// 使用自定義錯誤類
		throw new ApiError(
			ApiError.PARAMETER_FORMAT_ERROR,
			ApiError.PARAMETER_FORMAT_ERROR_DESC,
		);
	}
	
	try {
		let stat = await fs.stat(filepath);		
		// 檢查對應的文件是否爲文件類型
		if (!stat.isFile()) {
			throw new ApiError(
				ApiError.FILE_FORMAT_ERROR,
				ApiError.FILE_FORMAT_ERROR_DESC,
			);
		}
		let content = await fs.readFile(filepath);

		return content;
	}
	catch (err) {
		// 檢查文件是否存在
		if (err.code === 'ENOENT') {
			throw new ApiError(
				ApiError.FILE_NOT_FOUND_ERROR,
				ApiError.FILE_NOT_FOUND_ERROR_DESC,
			);
		}

		throw err;
	}
}
複製代碼
  1. 對於promise的異常處理,千萬不要爲了'安全起見'把全部函數都.catch,這可能會致使exception被吞掉,查錯時找不到異常信息

    1. 對於須要catch的promise,儘可能先處理異常,處理不了的,再向後拋

    2. Promise.reject(error)代替throw error,更優雅

    3. promise的then(resolve, reject)then(resolve, null).catch()的區別

    4. promise.catch裏若是沒有再rejectthrow,以後邏輯會走到resolve裏而非reject

      Promise.resolve()
      	.then(() => {
      		console.log(a); // 這一行報錯,會被catch接住
      	})
      	.catch(err => {
      		console.log(err);
      		return 1;
      	})
      	.then(ret => {
      		console.log(ret); // 會執行,且打印 1
      	});
      複製代碼

REFERENCE

  1. Java 異常進階
  2. 處理JavaScript異常的正確姿式
  3. Error
相關文章
相關標籤/搜索