💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、豆包、星火、月之暗面及文生图、文生视频 广告
[TOC] # Error > 在开发过程中,经常需要我们自己的错误类,用于描述任务中可能发生错误的特别内容。如网络操作错误,可能需要`HttpError`,数据库操作错误`DbError`以及搜索操作错误`NotFoundError`等等。<br> 我们的错误应该支持基本的错误属性,如:`message`,`name`以及更详细的`stack`,也可能有其他属性,如`HttpError`对象可能有`statusCode`属性,如:`404`、`403`、`500`。<br> Javascript使用throw可以带任何参数,所以技术上自定义错误不需要继承`Error`,但通过继承,可以使用`obj instanceof Error`去区别错误对象,所以最好使用继承。<br> 当我们搭建应用时,我们的错误自然形成层次结构,举例,`HttpTimeoutError`可能继承自`HttpError`等。 <br> ## 扩展Error 举例,我们考虑函数`readUser(json)`可以读取用户数据,下面是一个有效的json数据。 ~~~ let json = `{ "name": "John", "age": 30 }`; ~~~ <br> 使用`JSON.parse`,如果接收到畸形的json,则会抛出`SyntaxError`错误。 <br> 函数`readUser(json)`应该不仅读json,也应该检查数据。如果没有必须的属性或格式错误,则为错误,我们称为`ValidationError`。 ~~~ class ValidationError extends Error { constructor(message) { super(message); // 调用父类构造函数 this.name = "ValidationError"; // 设置name属性 } } ~~~ <br> 尝试在`readUser(json)`中使用。注意使用 `instanceof` 检查特定的错误类型 ~~~ 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 (**) } } ~~~ <br> ## 进一步继承 创建更具体类`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 } } function readJSON (data) { var user = JSON.parse(data) if (!user.age) { throw new PropertyRequiredError('age') } if (!user.name) { throw new PropertyRequiredError('name') } return user } try { let user = readJSON('{ "age": 25 }') } catch (err) { if (err instanceof ValidationError) { alert(`Invalid data: ${err.message}`) } else if (err instanceof SyntaxError) { alert(`JSON Syntax Error: ${err.message}`) } else { throw err } } ~~~ <br> 请注意在`PropertyRequiredError`构造函数再次手工给`this.name`赋值。这可能有点冗长,创建每个自定义错误都需要赋值:`this.name = <class name>`,但有其他方法,我们创建我们自己的基础错误类,通过赋值`this.constructor.name`给`this.name`,然后再从该类继承会简化。 ~~~ class MyError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } class ValidationError extends MyError { constructor(message) { super(message) } } class PropertyRequiredError extends ValidationError { constructor (property) { super(`No property ${property}`) this.property = property } } ~~~ <br> ## 包装异常 在处理的过程中有可能有其他错误发生。现在我们有了`SyntaxError`和`ValidationError`,但未来,`readUser`函数可能会扩展:新代码可能会生成其他类型的错误。 <br> 我们创建一个`ReadError`来表现这些错误,如果在`readUser`内部发生错误,捕获并生成`ReadError`错误。同时保留原始错误的引用至`cause`属性。那么外部代码仅需要检查`ReadError`。 <br> 下面代码定义`ReadError`并演示在`try...catch`块中使用`readUser`。 ~~~ class MyError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } class ReadError extends MyError { constructor(message, cause) { super(message) this.cause = cause } } class ValidationError extends MyError { constructor(message) { super(message) } } class PropertyRequiredError extends ValidationError { constructor(property) { super(`No property ${property}`) this.property = property } } 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) alert(`Original error: ${e.cause}`) } else { throw e } } ~~~ 上面代码中,`readUser`如描述的一样工作正常——捕获syntax和validation错误,然后抛出ReadError错误,代替之前的未知错误重新抛出。 <br> 所以外部代码检查`instanceof ReadError`,无需列出所有类型的错误。 <br> 这种方法称为“包装异常”,因为我们获得“低级别的异常”并包装至`ReadError`,对调用代码来说,更抽象更方便。在面向对象编程中广泛使用。 <br> ## 总结 * 通常可以从`Error`或其他的内置错误类中继承,只需关心name属性,不要忘记调用super。 * 大多数时,应该使用instanceof检查特定错误,也支持继承类。但有时有错误对象来自第三方库,不容易获得其类,那么name属性可以被使用。 * 包装异常被普遍使用,当函数处理低级别异常,并使一个更高级别的对象报告错误,低级别异常有时编程对象属性,如上面示例中的`err.cause`,但没有严格规定。 <br> <br> # Throw ~~~ throw expression; ~~~ **throw**语句用来抛出一个用户自定义的异常。当前函数的执行将被停止(**throw**之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个**catch**块。如果调用者函数中没有**catch**块,程序将会终止。 ~~~ try { console.log('before throw error'); throw new Error('throw error'); console.log('after throw error'); } catch (err) { console.log(err.message); } // before throw error // throw error ~~~ <br> <br> # try / catch / finally <br> ~~~ try { try_statements } [catch (exception) { catch_statements }] [finally { finally_statements }] ~~~ <br> ## 只能捕捉运行时错误 **try/catch**主要用于捕捉异常。**try/catch**语句包含了一个**try**块, 和至少有一个**catch**块或者一个**finally**块,下面是三种形式的**try**声明: * try...catch * try...finally * try...catch...finally <br> **try**块中放入可能会产生异常的语句或函数 <br> **catch**块中包含要执行的语句,当**try**块中抛出异常时,**catch**块会捕捉到这个异常信息,并执行**catch**块中的代码,如果在**try**块中没有异常抛出,这**catch**块将会跳过。 <br> **finally**块在**try**块和**catch**块之后执行。无论是否有异常抛出或着是否被捕获它总是执行。当在**finally**块中抛出异常信息时会覆盖掉**try**块中的异常信息。 <br> ## 不能捕获异步代码错误 要注意的是try catch只能捕获同步代码的异常,对回调,setTimeout,promise等无能为力 ~~~ try { setTimeout(() => { throw new Error("some message"); }, 0); } catch (err) { console.log(err); } // Uncaught Error: some message ~~~ <br> ## Try / Catch 性能 有一个大家众所周知的反优化模式就是使用**try/catch**。 在V8(其他JS引擎也可能出现相同情况)函数中使用了**try/catch**语句不能够被V8编译器优化。参考[http://www.html5rocks.com/en/tutorials/speed/v8/](http://www.html5rocks.com/en/tutorials/speed/v8/) <br> <br> # window.onerror error事件的事件处理程序。针对各种目标的不同类型的错误触发了 Error 事件: * 当JavaScript运行时错误(包括语法错误)发生时,window会触发一个ErrorEvent接口的error事件,并执行window.onerror()。 * 当一项资源(如`<img>`或`<script>`)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的`onerror()`处理函数。这些error事件**不会向上冒泡到window**,不过(至少在Firefox中)能被单一的`window.addEventListener`捕获。 <br> 加载一个全局的`error`事件处理函数可用于自动收集错误报告。 <br> ~~~ window.onerror = function (message, source, lineno, colno, error) { } ~~~ * `message`:异常信息(字符串) * `source`:发生异常的脚本URL(字符串) * `lineno`:发生异常的行号(数字) * `colno`:发生异常的列号(数字) * `error`:Error对象(对象) <br> **若该函数返回`true`,则阻止执行默认事件处理函数。** <br> 注意:Safari 和 IE10 还不支持在**window.onerror**的回调函数中使用第五个参数,也就是一个**Error**对象并带有一个追溯栈。 <br> ## 捕获语法错误 window.onerror能捕捉到语法错误,但是语法出错的代码块不能跟window.onerror在同一个块。 只要把window.onerror这个代码块分离出去,并且比其他脚本先执行即可捕捉到语法错误。 错误处理代码 ~~~ window.onerror = (msg, url, line, col, err) => { console.log(msg); console.log(url); console.log(line); console.log(col); console.dir(err); // return true } ~~~ 业务代码 ~~~ // 语法错误 alert(1 // 异步错误 setTimeout(() => { throw new Error("some message"); }, 0); // 运行时错误 i < 1 ~~~ ## 跨域资源 当加载自不同域的脚本中发生语法错误时,语法错误的细节将不会报告,而代之简单的"Script error."。在某些浏览器中,通过在`<script>`使用 **`crossorigin`** 属性并要求服务器发送适当的 **CORS HTTP** 响应头,该行为可被覆盖。一个变通方案是单独处理"Script error.",告知错误详情仅能通过浏览器控制台查看,无法通过JavaScript访问。 <br> # Promise中的异常 ## Promise中抛出异常 ~~~ new Promise((resolve,reject)=>{ reject(); }) ~~~ ~~~ Promise.resolve().then((resolve,reject)=>{ reject(); }); ~~~ ~~~ Promise.reject(); ~~~ ~~~ throw expression; ~~~ <br> ## Promise中捕捉异常 ~~~ promiseObj.then(undefined, (err)=>{ catch_statements }); ~~~ ~~~ promiseObj.catch((exception)=>{ catch_statements }) ~~~ <br> # window.onunhandledrejection `window.onunhandledrejection`与`window.onerror`类似,在一个JavaScript Promise 被**reject**但是没有**catch**来捕捉这个**reject**时触发。并且同时捕获到一些关于异常的信息。 <br> ~~~ window.onunhandledrejection = event => { event.preventDefault(); // 阻止触发错误 console.log(event.reason); // 也可以使用 return true 阻止错误 } ~~~ <br> `event`事件是**PromiseRejectionEvent**的实例,它有两个属性: * `event.promise`:被 rejected 的 JavaScript Promise * `event.reason`:一个值或 Object 表明为什么 promise 被 rejected,是**Promise.reject()**中的内容。 <br> # window.rejectionhandled 因为**Promise**可以延后调用**catch**方法,若在抛出**reject**时未调用**catch**进行捕捉,但稍后再次调用**catch**,此时会触发**rejectionhandled**事件。 ~~~ window.onrejectionhandled = event => { console.log('rejection handled'); } let p = Promise.reject(new Error('throw error')); setTimeout(()=>{ p.catch(e=>{console.log(e)}); },1000); // Uncaught (in promise) Error: throw error // 1秒后输出 // Error: throw error // rejection handled ~~~ <br> <br> # 参考资料 [前端代码异常监控方案window.onerror](https://blog.csdn.net/wangji5850/article/details/51180314) [JavaScript的异常处理](https://segmentfault.com/a/1190000011481099) [Javascript自定义错误,继承Error](https://blog.csdn.net/neweastsun/article/details/76371061)