JS异常处理

最近node写的比较多,后台应用你懂的,一个异常没处理好,分分钟crash给你看。在开发过程中总结了一些经验,分享给大家

Error类

Error类的用法很简单,new或者直接把Error当成function来用都行,然后在你认为需要抛出异常的地方throw它。

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的捕获方式

Promise里我们一般在最后加一个.catch,用来处理整个Promise执行链路中任何可能出现的异常,比如:

Promise.resolve()
	.then(() => {
		console.log(a); // 这里会出现异常
	})
	.then(() => {
		console.log(‘hi‘); // 这里不会执行
	})
	.catch(err => {
		console.log(err); // ReferenceError
	});
复制代码

await语法返回的也是Promise对象,不过你可以通过try...catch语法来接住异常

async function sayHi() {
	try {
		let ret = await anotherPromiseFunction();
	}
	catch (err) {
		console.log(err); // anotherPromiseFunction抛出的异常在这里处理
	}
}
复制代码

如何优雅的抛出异常

  1. 你需要一个自定义错误类。JS原生的错误类型只能定义基本的语言类异常,而我们在业务代码中,需要频繁地定义、抛出一些与业务强相关的异常,比如:

校验验证码的api,验证码格式不对时需要抛出一个异常,这个异常应该是跟校验相关的,且调用者能清晰解读并且能够根据错误信息做出相应处理的。

我的自定义错误类:

/**
 * @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 = ‘查询数据失败‘;

// ......

复制代码

构造函数有两个参数,codemessage

  • 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
      	});

 

JS异常处理

上一篇:linux localhost 借助nginx 支持https


下一篇:webpack