相關博客:
js中的錯誤監控(一)【promise,async,generator異步+內置錯誤類型】的錯誤捕獲與上報
js中的錯誤監控(二)【網絡+資源加載】的錯誤捕獲與上報
文章目錄
一、錯誤類型
主要分成兩類——代碼運行錯誤
和資源請求錯誤
1.代碼運行錯誤
運行代碼時發生的錯誤有非常多。每種錯誤都有相應的錯誤類型。ECMA-262定義了7種錯誤類型:
1.Error 錯誤,通用的異常對象。
Error是基類型。其它類型繼承自它。我們通常使用Error來自定義異常,Error對象有name和message屬性,可以通過message來得到具體的錯誤信息,比如
let error = new Error('接口報錯');
let name = error.name; // 'Error'
let msg = error.message; // '接口報錯'
2.EvalError 調用eval()函數拋出錯誤
3.RangeError 引用錯誤
超出指定範圍錯誤,比如聲明一個負數的數組,使用toFixec超過了規定小數的位數(0-20)
new Array(-1)
(1.2).toFixed(21)
4.ReferenceError 參數錯誤,訪問未定義的變量
function foo() {
bar++; // bar未定義
}
5.SyntaxError 語法錯誤,一般代碼語句不完整
let a = 1 > 0 ? // 正則不完整
if (a) { // 少了一個分號
6.TypeError 類型錯誤,一個變量不是函數,卻把它當做函數來調用
let a = 1;
a(); // 類型錯誤
7.URIError 編碼錯誤,在使用encodeURI()和decodeURI()時。假設URI格式不對時,會導致URIError錯誤。
encodeURI('\uD800')
encodeURIComponent('\uD800')
2.資源加載錯誤
這個錯誤通常是找不到文件(404)或者是文件加載超時造成的。
詳見:
js中的錯誤監控(二)【網絡+資源加載】的錯誤捕獲與上報
二、錯誤冒泡
錯誤也具有冒泡傳播性
如果在一個函數內部發生了錯誤,它自身沒有捕獲,錯誤就會被拋到外層調用函數,如果外層函數也沒有捕獲,該錯誤會一直沿着函數調用鏈
向上拋出,直到被JavaScript引擎捕獲
,代碼終止執行。
所以,我們不必在每一個函數內部捕獲錯誤,只需要在合適的地方來個統一捕獲,一網打盡
三、捕獲代碼錯誤
比如語法錯誤、邏輯錯誤,針對不會被代碼檢查插件發現的錯誤
1. try…catch語句
我們可以通過try/catch語法來捕捉錯誤。最常用的是在函數裏面捕捉錯誤,有錯誤就在catch處理。
try {
throw new Error("接口錯誤");
console.log("try錯誤後面的語句不會被執行");
} catch (error) {
// 捕獲錯誤
console.log("try-catch捕獲", error);
} finally {
//finally爲了防止出現異常後。無法往下再運行的備用。
console.log("finally 我都會運行!");
}
console.log("try-catch後面的語句");
控制檯:
捕捉內置對象類型:
//try-catch
try
// 運行可能出錯的代碼
a++;
console.log("try錯誤後面的語句不會被執行");
} catch (error) {
// 捕獲錯誤
console.log("try-catch捕獲", error);
}
console.log("try-catch後面的語句");
控制檯:
只有一個message
信息量較少,可以通過新建一個自動義錯誤類,可以拿到出錯的信息,堆棧,出錯的文件、行號、列號;
捕捉自定義error:
// Create a custom error
var SpecifiedError = function SpecifiedError(message) {
this.name = 'SpecifiedError';
this.message = message || '';
this.stack = (new Error()).stack;
};
SpecifiedError.prototype = new Error();
SpecifiedError.prototype.constructor = SpecifiedError;
缺點:
- 無法捕捉到
SyntaxError語法錯誤
,只能捕其他錯誤類型;如果嘗試捕獲語法錯誤,try-catch
不會生效
try {
// 運行可能出錯的代碼
let b = 1;
let b = 2; //SyntaxError不可以被捕獲
console.log("try錯誤後面的語句不會被執行");
} catch (error) {
// 捕獲錯誤
console.log("try-catch捕獲", error);
} finally {
//finally爲了防止出現異常後。無法往下再運行的備用。
console.log("finally 我都會運行!");
}
console.log("try-catch後面的語句");
控制檯:
- try catch只能捕獲
同步代碼的異常
,對回調,setTimeout,promise等無能爲力 - 對每一個函數都需要進行try/catch捕捉再進行處理,需要寫很多
重複的代碼
- 能夠通過改動代碼解決或瀏覽器兼容錯誤。不建議使用try-catch,由於它比一般語句
消耗資源
2. window.onerror事件
使用一個全局的error事件來捕獲所有的error,通過回調函數來處理錯誤,使用三個參數來調用:msg(錯誤消息)、url(發生錯誤的頁面的 url)、line(發生錯誤的代碼行)。
function message() {
adddlert("Welcome guest!");
}
window.onerror = function(message, source, lineno, colno, error) {
// 錯誤信息,源文件,行號
console.log(message + "\n" + source + "\n" + lineno);
// 禁止瀏覽器打印標準的錯誤信息
return true;
};
控制檯:
3.異步代碼錯誤
一般情況下:異步錯誤只能在異步的函數內部捕獲和處理,因爲出現了異步操作時,不會立即執行,js引擎繼續執行同步操作,捕獲錯誤就是一個同步操作。也就是說在捕獲錯誤的時候,異步操作裏的錯誤還沒發生
,所以需要在異步函數的內部去捕獲。
ES6中爲了處理異步,增加了promise、generator和async,它們各自都有不同的內部錯誤處理方式
try/catch
無法捕捉異步函數整體
拋出的錯誤:
try {
setTimeout(() => {
throw new Error('async error')
}, 0)
} catch (e) {
console.log(e.message)
}
// async/await
async function foo () {
let a = 1;
let b = await a + 2;
console.log(b);
throw new Error('async error')
}
try {
foo();
} catch (e) {
console.log(e.message);
}
控制檯報錯Uncaught Error: async error
:
正確的做法:
1.
try/catch
捕捉異步函數內部
拋出的錯誤
2..catch
捕捉promise異步整體錯誤
,遵循鏈式法則
3.1.setTimeout
try-catch捕獲setTimeout回調函數
內部的錯誤:
// 在異步代碼塊裏面的同步代碼就可以捕捉到
setTimeout(() => {
try {
throw new Error("async error");
} catch (e) {
console.log("setTimeout回調裏面的錯誤", e.message);
}
}, 0); //setTimeout回調裏面的錯誤 async error
3.2.promise
new Promise( function(resolve, reject) {...} /* executor */ );
executor
是帶有 resolve
和 reject
兩個參數的函數 ,Promise構造函數執行時(在實例化之前)立即調用
executor 函數,executor 內部通常會執行一些異步操作,一旦異步操作執行完畢(可能成功/失敗), resolve 和 reject 函數被調用時,分別將promise的狀態改爲fulfilled(完成)或rejected(失敗)。
resolve 和 reject 函數前後面的代碼會同步執行
,resolve 和 reject 函數作爲回調異步執行
,例子:
//resolve()以後的語句會被執行
var promise3 = new Promise((resolve, reject) => {
resolve("promise3-res");
console.log("promise3-resolve以後");
});
promise3.then(res => {
console.log(res);
});
//promise3-resolve以後
//promise3-res
//reject()以後的語句會被執行,
// 直接reject(“錯誤信息”)不可以被.catch捕捉,會報錯Uncaught (in promise)
var promise4 = new Promise((resolve, reject) => {
reject("promise4-res-catch");
console.log("promise4-resolve以後");
});
promise3.catch(res => {
console.log(res);
});
// promise4-resolve以後
//promise.js:9 Uncaught (in promise) promise4-res-catch
注意:直接reject(“錯誤信息”)不可以被.catch捕捉,會報錯Uncaught (in promise),接下來馬上會講到reject的正確使用方法。
1⃣️捕捉executor裏的錯誤:
如果在executor函數中throw一個錯誤,那麼該promise 狀態變爲rejected。executor函數返回值會被忽略,throw之後的代碼不會被執行,.catch無法捕捉錯誤
:
//throw new Error()使得該promise 狀態爲rejected。executor函數的返回值被忽略。
//throw之後的代碼不會被執行
var promise6 = new Promise((resolve, reject) => {
throw new Error("promise6-res-catch");
console.log("promise6-resolve以後");
});
promise5.catch(function(e) {
console.log(e);
});
// Uncaught (in promise) Error: promise6-res-catch
方法一:reject(new error())
將error對象傳給reject回調,.catch可以捕獲錯誤
//reject(new error())可以被.catch捕捉錯誤,能獲取返回值
var promise5 = new Promise((resolve, reject) => {
reject(new Error("promise5-res-catch"));
});
promise5.catch(function(e) {
console.log(e); //Error: promise5-res-catch
});
方法二: try-catch捕獲promise同步函數
內部的錯誤,然後通過 reject(e)
傳遞給.catch
方法
var promise = new Promise(function(resolve, reject) {
try {
throw new Error("Promise同步語句裏面的錯誤");
} catch (e) {
reject(e);
}
});
promise
.then(res => {
console.log("resolve");
})
.catch(e => {
console.log("promise-reject", e); //promise-reject Error: Promise同步語句裏面的錯誤
});
2⃣️捕捉.then
.catch
鏈式回調裏面的錯誤
方法一:.catch
方法還可以處理鏈式調用.then
中的錯誤,只處理第一個錯誤:
var promise2 = new Promise(function(resolve, reject) {
resolve();
});
promise2
.then(function() {
// if some error throw
throw new Error("Promise.then裏面的錯誤1");
})
.then(function() {
// if some error throw
throw new Error("Promise.then裏面的錯誤2");
})
.catch(function(e) {
//something to deal with the error
console.log(e); //Promise.then裏面的錯誤1
});
catch方面裏面還可以再拋錯誤,這個錯誤會被後面的catch捕獲。
3⃣️promise無法被捕捉的錯誤
- 如果組成Promise.all的promise有自己的錯誤捕獲方法,那麼Promise.all中的catch就不能捕獲該錯誤。
- 在promise實例resolve之後,錯誤無法被捕獲。
var p1=new Promise(function(resolve,reject){
reject(new Error('test1'))
}).catch(function(e){
console.log("由p1自身捕獲",e);
})
var p2=new Promise(function(resolve,reject){
resolve();
})
var p=Promise.all([p1,p2]);
p.then(function(){
}).catch(function(e){
//在此處捕獲不到p1中的error
console.log(e)
})
//由p1自身捕獲 Error: test1
3.3.async-await
trycatch直接捕獲async-await同步語句
的錯誤:
async function foo() {
try {
let a = 1;
let b = (await a) + 2;
console.log("async-await,b", b);
throw new Error("async同步語句裏面的錯誤");
} catch (e) {
console.log("async-await", e.message); //async同步語句裏面的錯誤 async error
}
}
foo();
此外,async裏面throw Error 相當於返回Promise.reject。所以也可以直接在內部/外部使用.catch
捕捉錯誤
//async.catch
async function F1() {
throw new Error("f1-async裏的錯誤,相當於執行reject(e)");
}
var f1 = F1();
f1.catch(function(e) {
console.log(e); //Error: f1-async裏的錯誤,相當於執行reject(e)
});
async function F2() {
await Promise.reject("f2-async裏的錯誤,相當於執行reject(e)").catch(function(
e
) {
console.log(e);
});
}
var f2 = F2(); // f2-async裏的錯誤,相當於執行reject(e)
3.4.gennerator
try-catch捕獲gennerator.throw
的錯誤
Generator 函數返回的遍歷器對象,都有一個throw方法,可以在函數體外拋出錯誤,然後在 Generator 函數體內捕獲。
如果Generator的錯誤沒有被捕獲,就不會繼續執行
,只要錯誤沒有被處理,就會返回done:true
就停止執行generator
// gennerator
function* F() {
try {
yield 1;
} catch (e) {
console.log("gennerator-throw", e);
}
yield 2;
return "gennerator-value";
}
var f = F();
f.next(); //{value :1,done:false}
//在函數體外拋出錯誤,然後在 Generator 函數體內捕獲。
f.throw(new Error("gennerator-throw在函數體外拋出的錯誤")); //{value:2,done:false}
f.next(); //{value:3,done:true}
詳見 http://es6.ruanyifeng.com/#docs/generator#Generator-prototype-throw
內部錯誤的控制檯結果:先執行同步操作,所以f.throw
先執行,被捕獲,然後async函數的異步操作
,所以3先被打印,然後執行async的await回調
(相當於promise微事件),async的catch再被打印,再執行promise-reject
,最後執行setTimeout的回調,所以最後打印setTimeout的catch
參考:https://blog.csdn.net/liwusen/article/details/79617903
https://www.cnblogs.com/fundebug/p/7989066.html