[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)
- 第一部分 HTML
- meta
- meta标签
- HTML5
- 2.1 语义
- 2.2 通信
- 2.3 离线&存储
- 2.4 多媒体
- 2.5 3D,图像&效果
- 2.6 性能&集成
- 2.7 设备访问
- SEO
- Canvas
- 压缩图片
- 制作圆角矩形
- 全局属性
- 第二部分 CSS
- CSS原理
- 层叠上下文(stacking context)
- 外边距合并
- 块状格式化上下文(BFC)
- 盒模型
- important
- 样式继承
- 层叠
- 属性值处理流程
- 分辨率
- 视口
- CSS API
- grid(未完成)
- flex
- 选择器
- 3D
- Matrix
- AT规则
- line-height 和 vertical-align
- CSS技术
- 居中
- 响应式布局
- 兼容性
- 移动端适配方案
- CSS应用
- CSS Modules(未完成)
- 分层
- 面向对象CSS(未完成)
- 布局
- 三列布局
- 单列等宽,其他多列自适应均匀
- 多列等高
- 圣杯布局
- 双飞翼布局
- 瀑布流
- 1px问题
- 适配iPhoneX
- 横屏适配
- 图片模糊问题
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 内存空间
- 作用域
- 执行上下文栈
- 变量对象
- 作用域链
- this
- 类型转换
- 闭包(未完成)
- 原型、面向对象
- class和extend
- 继承
- new
- DOM
- Event Loop
- 垃圾回收机制
- 内存泄漏
- 数值存储
- 连等赋值
- 基本类型
- 堆栈溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍历对象属性
- 宽度、高度
- performance
- 位运算
- tostring( ) 与 valueOf( )方法
- JavaScript技术
- 错误
- 异常处理
- 存储
- Cookie与Session
- ES6(未完成)
- Babel转码
- let和const命令
- 变量的解构赋值
- 字符串的扩展
- 正则的扩展
- 数值的扩展
- 数组的扩展
- 函数的扩展
- 对象的扩展
- Symbol
- Set 和 Map 数据结构
- proxy
- Reflect
- module
- AJAX
- ES5
- 严格模式
- JSON
- 数组方法
- 对象方法
- 函数方法
- 服务端推送(未完成)
- JavaScript应用
- 复杂判断
- 3D 全景图
- 重载
- 上传(未完成)
- 上传方式
- 文件格式
- 渲染大量数据
- 图片裁剪
- 斐波那契数列
- 编码
- 数组去重
- 浅拷贝、深拷贝
- instanceof
- 模拟 new
- 防抖
- 节流
- 数组扁平化
- sleep函数
- 模拟bind
- 柯里化
- 零碎知识点
- 第四部分 进阶
- 计算机原理
- 数据结构(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 搜索算法
- 动态规划
- 二叉树
- 浏览器
- 浏览器结构
- 浏览器工作原理
- HTML解析
- CSS解析
- 渲染树构建
- 布局(Layout)
- 渲染
- 浏览器输入 URL 后发生了什么
- 跨域
- 缓存机制
- reflow(回流)和repaint(重绘)
- 渲染层合并
- 编译(未完成)
- Babel
- 设计模式(未完成)
- 函数式编程(未完成)
- 正则表达式(未完成)
- 性能
- 性能分析
- 性能指标
- 首屏加载
- 优化
- 浏览器层面
- HTTP层面
- 代码层面
- 构建层面
- 移动端首屏优化
- 服务器层面
- bigpipe
- 构建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack优化
- Webpack原理
- 实现loader
- 实现plugin
- tapable
- Webpack打包后代码
- rollup.js
- parcel
- 模块化
- ESM
- 安全
- XSS
- CSRF
- 点击劫持
- 中间人攻击
- 密码存储
- 测试(未完成)
- 单元测试
- E2E测试
- 框架测试
- 样式回归测试
- 异步测试
- 自动化测试
- PWA
- PWA官网
- web app manifest
- service worker
- app install banners
- 调试PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 饿了么整理
- 样式
- 技巧
- Vue音乐播放器
- Vue源码
- Virtual Dom
- computed原理
- 数组绑定原理
- 双向绑定
- nextTick
- keep-alive
- 导航守卫
- 组件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 动画(未完成)
- 异常监控、收集(未完成)
- 数据采集
- Sentry
- 贝塞尔曲线
- 视频
- 服务端渲染
- 服务端渲染的利与弊
- Vue SSR
- React SSR
- 客户端
- 离线包
- 第五部分 网络
- 五层协议
- TCP
- UDP
- HTTP
- 方法
- 首部
- 状态码
- 持久连接
- TLS
- content-type
- Redirect
- CSP
- 请求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服务端
- Linux
- Linux命令
- 权限
- XAMPP
- Node.js
- 安装
- Node模块化
- 设置环境变量
- Node的event loop
- 进程
- 全局对象
- 异步IO与事件驱动
- 文件系统
- Node错误处理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服务
- 负载均衡
- 获取用户IP
- 解决跨域
- 适配PC与移动环境
- 简单的访问限制
- 页面内容修改
- 图片处理
- 合并请求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自动化(未完成)
- docker
- 创建CLI
- 持续集成
- 持续交付
- 持续部署
- Jenkins
- 部署与发布
- 远程登录服务器
- 增强服务器安全等级
- 搭建 Nodejs 生产环境
- 配置 Nginx 实现反向代理
- 管理域名解析
- 配置 PM2 一键部署
- 发布上线
- 部署HTTPS
- Node 应用
- 爬虫(未完成)
- 例子
- 反爬虫
- 中间件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源码
- app.js
- config.js
- 消息队列
- RPC
- 性能优化
- 第七部分 总结
- Web服务器
- 目录结构
- 依赖
- 功能
- 代码片段
- 整理
- 知识清单、博客
- 项目、组件、库
- Node代码
- 面试必考
- 91算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码