[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算法
- 第八部分 工作代碼總結
- 樣式代碼
- 框架代碼
- 組件代碼
- 功能代碼
- 通用代碼