Javascript自定义错误,继承Error

在开发过程中,经常需要我们自己的错误类,用于描述任务中可能发生错误的特别内容。如网络操作错误,可能需要HttpError,数据库操作错误DbError以及搜索操作错误NotFoundError等等。

我们的错误应该支持基本的错误属性,如:message,name以及更详细的stack,也可能有其他属性,如HttpError对象可能有statusCode属性,如:404403500

Javascript使用throw可以带任何参数,所以技术上自定义错误不需要继承Error,但通过继承,可以使用obj instanceof Error去区别错误对象,所以最好使用继承。

当我们搭建应用时,我们的错误自然形成层次结构,举例,HttpTimeoutError可能继承自HttpError等。

扩展Error

举例,我们考虑函数readUser(json)可以读取用户数据,下面是一个有效的json数据。

let json = `{ "name": "John", "age": 30 }`;

我们自然使用JSON.parse,如果接收到畸形的json,则会抛出SyntaxError错误。

即使json是语法正确的,也不意味是有有效的user,对吗?可以丢失有效的数据,举例,如果没有基本的name和age属性。

函数readUser(json)应该不仅读json,也应该检查(验证)数据。如果没有必须的属性或格式错误,则为错误,这不属于SyntaxError,因为数据语法正确,而是另一种类型错误,我们称为ValidationError,需要为之创建类,其也应该能承载缺失字段信息。

我们的ValidationError类应该继承自内置的Error类:

该类是内置的,但是在我们眼里应该大致的代码,来理解我们在扩展什么。代码如下:

// The "pseudocode" for the built-in Error class defined by JavaScript itself
class Error {
  constructor(message) {
    this.message = message;
    this.name = "Error"; // (different names for different built-in error classes)
    this.stack = <nested calls>; // non-standard, but most environments support it
  }
}

现在,让我们继续并从它继承类ValidationError

class ValidationError extends Error {
  constructor(message) {
    super(message); // (1)
    this.name = "ValidationError"; // (2)
  }
}

function test() {
  throw new ValidationError("Whoops!");
}

try {
  test();
} catch(err) {
  alert(err.message); // Whoops!
  alert(err.name); // ValidationError
  alert(err.stack); // a list of nested calls with line numbers for each
}

请看构造函数:

  1. 行(1)我们调用父类构造函数,Javascript需要我们在子类构造函数中调用super,这时强制的。父类构造函数设置message属性。
  2. 父类构造函数也设置name属性为Error,所以行(2)我们重置为正确的值。

现在尝试在readUser(json)中使用。

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

// Usage
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new ValidationError("No field: age");
  }
  if (!user.name) {
    throw new ValidationError("No field: name");
  }

  return user;
}

// Working example with try..catch

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    alert("Invalid data: " + err.message); // Invalid data: No field: name
  } else if (err instanceof SyntaxError) { // (*)
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // unknown error, rethrow it (**)
  }
}

代码块try...catch处理ValidationError错误和内置的SyntaxError

请看星号行,我们使用instanceof检查特定的错误类型,我们也可以看到err.name方式,如下:

// ...
// instead of (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...

使用instanceof方式更好,因为未来可以可能会继续扩展ValidationError创建子类,如PropertyRequiredError.instanceof方式对新继承类仍正常工作,所以不会过时。

这也很重要,如果catch块中遇到未知错误,那么会重新抛出错误(在**行),catch块仅知道如何处理验证和语法错误,其他错误应该不捕获。

进一步继承

ValidationError很通过,很多情况可能会误解,属性缺失或格式错误(age值为字符串)。让我们创建更具体类PropertyRequiredError,针对属性缺失错误,包括具体那个属性缺失的额外信息。

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("No property: " + property);
    this.name = "PropertyRequiredError";
    this.property = property;
  }
}

// Usage
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new PropertyRequiredError("age");
  }
  if (!user.name) {
    throw new PropertyRequiredError("name");
  }

  return user;
}

// Working example with try..catch

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    alert("Invalid data: " + err.message); // Invalid data: No property: name
    alert(err.name); // PropertyRequiredError
    alert(err.property); // name
  } else if (err instanceof SyntaxError) {
    alert("JSON Syntax Error: " + err.message);
  } else {
    throw err; // unknown error, rethrow it
  }
}

新的类PropertyRequiredError也容易使用:仅需要传递属性名称:new PropertyRequiredError(property)。构造函数生成人类易懂的message属性值。

请注意在PropertyRequiredError构造函数再次手工给this.name赋值。这可能有点冗长,创建每个自定义错误都需要赋值:this.name = <class name>,但有其他方法,我们创建我们自己的基础错误类,通过赋值this.constructor.namethis.name,然后再从该类继承会简化。

我们称之为:MyError.

下面是MyError及其他自定义类的代码:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
  }
}

class ValidationError extends MyError { }

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("No property: " + property);
    this.property = property;
  }
}

// name is correct
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError

现在自定义错误相当简短,特别是ValidationError, 因为构造行数中删除了该行:"this.name = ...".

包装异常

上面代码中的readUser函数的目的是读取用户数据,对吗?在处理的过程中有可能有其他错误发生。现在我们有了SyntaxErrorValidationError,但未来,readUser函数可能会扩展:新代码可能会生成其他类型的错误。

调用readUser函数的代码应该处理这些错误,现在catch块使用多个if检查不同类型的错误,然后重新抛出未知促我。但如果readUser函数产生几种不同类型的错误——那么我们要问自己,真的想通过一个一个检查所有的错误吗?

答案当然是“No”:外部代码想要“高于一切”的抽象错误类,如“数据读错误”。通常其错误描述信息不准确,或者,如果有一种获取错误细节的方法,那就更好了,但仅为如果需要时。

所以让我们创建一个ReadError来表现这些错误,如果在readUser内部发生错误,我们捕获并生成ReadError错误。同时保留原始错误的引用至cause属性。那么外部代码仅需要检查ReadError

下面代码定义ReadError并演示在try...catch块中使用readUser

class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = 'ReadError';
  }
}

class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }

function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError("age");
  }

  if (!user.name) {
    throw new PropertyRequiredError("name");
  }
}

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
    if (err instanceof SyntaxError) {
      throw new ReadError("Syntax Error", err);
    } else {
      throw err;
    }
  }

  try {
    validateUser(user);
  } catch (err) {
    if (err instanceof ValidationError) {
      throw new ReadError("Validation Error", err);
    } else {
      throw err;
    }
  }

}

try {
  readUser('{bad json}');
} catch (e) {
  if (e instanceof ReadError) {
    alert(e);
    // Original error: SyntaxError: Unexpected token b in JSON at position 1
    alert("Original error: " + e.cause);
  } else {
    throw e;
  }
}

上面代码中,readUser如描述的一样工作正常——捕获syntax和validation错误,然后抛出ReadError错误,代替之前的未知错误重新抛出。

所以外部代码检查instanceof ReadError,无需列出所有类型的错误。

这种方法称为“包装异常”,因为我们获得“低级别的异常”并包装至ReadError,对调用代码来说,更抽象更方便。在面向对象编程中广泛使用。

总结

  • 通常可以从Error或其他的内置错误类中继承,只需关心name属性,不要忘记调用super。
  • 大多数时,应该使用instanceof检查特定错误,也支持继承类。但有时有错误对象来自第三方库,不容易获得其类,那么name属性可以被使用。
  • 包装异常被普遍使用,当函数处理低级别异常,并使一个更高级别的对象报告错误,低级别异常有时编程对象属性,如上面示例中的err.cause,但没有严格规定。

文章出处:https://blog.csdn.net/neweastsun/article/details/76371061

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345