跳到主要内容

错误类型与自定义错误

内置错误类型各表示什么?​

类型含义
Error基类型
InternalError引擎内部错误
EvalErroreval() 相关错误
RangeError数值越界
ReferenceError找不到引用
SyntaxError语法错误 (如 eval() 中的非法语法)
TypeError变量不是预期类型
URIErrorencodeURI()/decodeURI() 相关错误

错误对象包含哪些属性?​

  • name: 错误名, 如 "ReferenceError";
  • message: 人类可读的错误说明;
  • stack: 非标准但普遍支持, 记录导致错误的嵌套调用序列, 供调试使用;
  • 构造: new Error(message)、new SyntaxError(message) 等; 内置错误的 name 就是构造器名;
  • 字符串化: 直接打印错误对象相当于 "name: message", 例如 String(err) 得 "ReferenceError: lalala is not defined";

如何区分与抛出错误?​

  • 区分: 用 instanceof 按类型分支处理;
try {
someFunction();
} catch (error) {
if (error instanceof TypeError) {
// 类型错误
} else if (error instanceof ReferenceError) {
// 引用错误
} else {
// 其它
}
}
  • 抛出: throw 可抛任意值, 建议抛出内置错误类型的实例;
throw new TypeError("What type of variable do you take me for?");

什么时候应该把错误重新抛出?​

  • 事实: catch 接到的是 try 里的所有错误, 不只你预期的那种;
  • 风险: 把编程错误 (如变量未定义) 当成数据错误处理, 会打印误导信息并掩盖真正的 bug;
  • 规则: 只处理自己认识的错误, 其余 throw err 原样上抛;
  • 识别: 优先用 instanceof (对子类也成立); 拿不到类时比较 err.name 或 err.constructor.name;
  • 后果: 从 catch 抛出的错误会穿过本层 try...catch, 交给外层处理或终止脚本;
try {
run();
} catch (err) {
if (err instanceof SyntaxError) console.log("JSON Error: " + err.message);
else throw err; // 不知道怎么处理, 往上抛
}

如何自定义与包装错误?​

  • 继承: 自定义错误类 extends Error, 这样 instanceof Error 依然成立; 还可继续向下派生形成层级;
  • 必做: 子类构造器先 super(message), 再把 this.name 改回类名 (父构造器会置为 "Error");
  • 附加字段: 子类可携带自己的信息, 如 HttpError 的 statusCode;
  • 减样板: 做个基类统一写 this.name = this.constructor.name, 子类就不必各自赋值;
class MyError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class ValidationError extends MyError {}
new ValidationError("bad").name; // "ValidationError"
  • 包装: 函数内部捕获多种低级错误, 改抛更抽象的 ReadError, 原始错误挂在 cause; 调用方只需判断一种类型, 需要细节时再读 cause, 不认识的错误照旧上抛;
class ReadError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = "ReadError";
}
}
// readUser: try { JSON.parse(json); } catch (err) { throw new ReadError("Syntax Error", err); }