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
相关文章
相关标签/搜索