js中的錯誤監控(一)【promise,async,generator異步+內置錯誤類型】的錯誤捕獲與上報

相關博客:

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是帶有 resolvereject 兩個參數的函數 ,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無法被捕捉的錯誤

  1. 如果組成Promise.all的promise有自己的錯誤捕獲方法,那麼Promise.all中的catch就不能捕獲該錯誤。
  2. 在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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章