likes
comments
collection
share

一文弄懂JavaScript的错误类型,捕获及上报

作者站长头像
站长
· 阅读数 17

1.错误类型和对象

  • 语法错误:代码中存在拼写错误,将导致程序完全或部分不能运行
  • 逻辑错误:代码语法正确,但执行结果不匹配预期

一文弄懂JavaScript的错误类型,捕获及上报

1.Error

  • 基础的错误对象,其他错误对象均继承它

![image-20221016131503525](/Users/yunmu/Library/Application Support/typora-user-images/image-20221016131503525.png)

<body>
  <button type="button" id="btnEx">执行</button>
  <script>
    function trace() {
      try {
        throw new Error("哦豁,错误哦");
      } catch (err) {
        console.log("err.name", err.name);
        console.log("err.message", err.message);
        console.log("err.stack", err.stack);
        console.log("err.constructor:", err.constructor);
      }
    }
    function b() {
      trace();
    }
    btnEx.onclick = b;
  </script>
</body>

执行结果如下:

一文弄懂JavaScript的错误类型,捕获及上报

2.EvalError

  • 历史遗孤,eval相关的错误
  • 产生:
    • 不是被直接调用或者被赋值
  • 理论上现代浏览器不会再抛出此错误

3.InternalError

  • 红宝书和MDN都有提到这个对象,可惜的是只有 firefox 支持
  • 产生:
    • 过多 case 语句
    • 正则表达式中括号过多
    • 递归过深等

4.RangeError

  • 当一个值不在其允许的范围或者集合中
  • 当传递一个不合法的length值作为 Array 构造器的参数创建数组
  • 传递错误值到数值计算方法( Number.toExponential()Number.toFixed(), Number.toPrecision()等)

5.ReferenceError

  • 一个不存在的变量被引用时发生的错误

6.SyntaxError

  • 解析语法上不合法的代码的错误
  • 不能被用户代码catchSyntaXError
btn.onclick = function () {
  try {
    const hhhh bbbb;
  } catch (e) {
    console.log('e.message:', e.message);
  }
}
  • 可以被用户代码捕获的
btn.onclick = function () {
  try {
    new Function(`const  hhhh bbbb`);
  } catch (e) {
    console.log("e.message:", e.message);
  }
};

7.TypeError

  • 值的类型非预期类型时发生的错误
undefined.abc
new 666()
"hello" in "hello yun"
const a = 1;
a = 2

8.URIError

  • 使用全局 URI 处理函数而产生的错误
  • encodeURLencodeURIComponentdecodeURIdecodeURIComponent 方法会产生URIError错误

9.AggregateError

  • 包含多个错误信息的错误
Promise.any([
  Promise.reject(new Error("error 1")),
  Promise.reject(new Error("error 2")),
]).catch(e => {
  console.log("e instanceof AggregateError:", e instanceof AggregateError);
  console.log("e.message:", e.message);
  console.log("e.name", e.name);
  console.log("e.errors", e.errors);
});

打印结果如下:

一文弄懂JavaScript的错误类型,捕获及上报

2.异常类型判断和自定义异常

throw

  • JavaScript中,throw关键字可以将错误抛出
  • 但是throw不仅仅只能抛出错误对象,还可以抛出基本类型数据
try {
	throw "错误字符串";
} catch (e) {
	console.log(typeof e, "==name==", e.name, "===", e);
}

try {
	throw 666;
} catch (e) {
	console.log(typeof e, "==name==", e.name, "===", e);
}

function UserException(message) {
	this.message = message;
	this.name = "UserException";
}

try {
  // 抛出对象
	throw new UserException("无效异常");
} catch (e) {
	console.log(typeof e, "==name==", e.name, "===", e);
}

// 尽量抛出内置的Error
function throwError(a) {
	if (a > 10) {
		throw new RangeError("值超出限制");
	}
}

try {
	throwError(15);
} catch (e) {
	console.log(e.name, "==e.message==", e.message);
}


// 异常无法处理,重新抛出
try {
	throw 500;
} catch (e) {
	if (e <= 50) {
		console.log("已处理");
	} else {
		// 异常无法处理,重新抛出
		throw e;
	}
}

一文弄懂JavaScript的错误类型,捕获及上报

自定义异常类型

ES6实现

class CustomError extends Error {
	constructor(foo = "bar", ...params) {
		super(...params);

		// 不是必需
		// if (Error.captureStackTrace) {
		// 	Error.captureStackTrace(this, CustomError);
		// }

		this.name = "CustomError";
		this.foo = foo;
		this.date = new Date();
	}
}

function trace() {
	try {
		throw new CustomError("baz", "bazMessage");
	} catch (e) {
		console.log("是否是MyError类型错误:", e instanceof CustomError);
		console.log("e.message:", e.message);
		console.log("e.name", e.name);
		console.log("e.foo", e.foo);
		console.log("e.date", e.date);
		console.log("e.stack:", e.stack);
	}
}

function b() {
	trace();
}

function a() {
	b();
}

a();

ES5实现

function MyError(message) {
	this.name = "MyError";
	this.message = message || "Default Message";
	// this.stack = (new Error()).stack; 和下面方法类似,也是拿到对应调用的堆栈信息
	/*
	 * Error.captureStackTrace(targetObject[, constructorOpt])
	 * 参数 targetObject -> 表示一个对象
	 * 参数 constructorOpt -> 表示对象的构造函数
	 * 在targetObject上创建一个.stack属性, 调用是返回一个调用 Error.captureStackTrace() 的位置的字符串。
	 */
	Error.captureStackTrace(this, MyError);
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

function trace() {
	try {
		throw new MyError("custom message");
	} catch (e) {
		console.log("是否是MyError类型错误:", e instanceof MyError); // true
		console.log("e.message:", e.message); // 'custom message'
		console.log("stack:", e.stack);
	}
}

function b() {
	trace();
}

function a() {
	b();
}

a();

异常类型的判断

1.instanceof

function testReferenceError() {
	try {
		let a = undefinedVariable;
	} catch (e) {
		console.log("instanceof ReferenceError :", e instanceof ReferenceError); // true
	}
}

testReferenceError();

2.constructor

function testTypeError() {
	// TypeError类型错误
	try {
		new 666();
	} catch (err) {
		console.log("constructor TypeError", err.constructor === TypeError); // constructor TypeError true
	}
}

function testError() {
	//Error
	try {
		throw new Error("哦豁,错误哦");
	} catch (err) {
		console.log("constructor Error", err.constructor === Error); // constructor Error true
	}
}

testTypeError();
testError();

3.error.name

function testURIError() {
    try {
        decodeURIComponent('%')
      } catch (e) {
        console.log("ErrorName:",e.name === "URIError")  
     
      }
}

testURIError(); // ErrorName: true

注意AggregateError如果使用instanceof判断会出错,此时可借助name属性

function print(e) {
	console.log("错误类型:", e.constructor.name);
	console.log("错误信息:", e.message);
	if (e.constructor.name == "AggregateError" && e.errors) {
		for (let i = 0; i < e.errors.length; i++) {
			console.log(`错误信息${i}:`, e.errors[i].message);
		}
	}
}

function testError() {
	//AggregateError
	Promise.any([
		Promise.reject(new Error("error 1")),
		Promise.reject(new Error("error 2")),
	]).catch((e) => {
		print(e);
	});
}

testError();

打印结果如下:

一文弄懂JavaScript的错误类型,捕获及上报

3.异常捕获

1.try catch

const str = "yunmu";

try {
	JSON.parse(str);
} catch (e) {
	console.log("解析字符串错误");
} finally {
	console.log("finally处理");
}


try {
	const a = {};
	a.b();
} catch (e) {
	console.log("捕捉到错误", e.message);
}

有些错误无法捕获

//编译时错误,还没有执行到try catch
try{
    dd.
}catch(e){
    console.log("捕捉到错误",e.message);
}


//try catch已经执行完毕,无法捕获,可在异步操作里面进行try catch捕获
try{
   setTimeout(()=>{
     const a = {};
     a.b();
   })
}catch(e){
    console.log("捕捉到错误",e.message);
}

2.window.onerror(捕获全局JS异常)

window.onerror = function (message, url, line, column, error) {
  console.log(
    "捕获到错误:",
    message,
    "==line:",
    line,
    "==column:",
    column,
    "==error:",
    error
  );
};

setTimeout(() => {
  a.b();
});

const f = {};
f.cc.ee;

执行结果如下:

一文弄懂JavaScript的错误类型,捕获及上报

引入跨域JS文件获取具体错误信息处理

  • script 引用 JS 文件时增加crossorigin="anonymous"的属性
  • 如果是动态加载的JS,可以写 script.crossOrigin = true
  • 并且要为JS资源文件增加 CORS 响应头(服务),一般的CDN网站都会将Access-Control-Allow-Origin配置为*

3. window.addEventListener("error")

  • window.onerrorwindow.addEventListener("error") 都可以捕获js错误
  • 但是使用addEventListener 可以捕获静态资源加载错误,但必须要设置捕获阶段,即第三个参数为true
  • 捕获到静态资源错误,无法区分404或者500,需结合服务端
<body>
		<div>script error 解决</div>

		<script>
      // 捕获不到静态资源加载错误
			window.onerror = function (message, url, line, column, error) {
				console.log(
					"window.onerror 捕获到错误:",
					message,
					"==line:",
					line,
					"==column:",
					column,
					"==error:",
					error
				);
			};

			window.addEventListener(
				"error",
				function (event) {
					console.log(
						"window.addEventListener:",
						event,
						"==e.error:",
						event.error
					);
					if (event.target && (event.target.src || event.target.href)) {
						//静态资源
						console.log({
							type: "error", //resource
							errorType: "resourceError",
							filename: event.target.src || event.target.href, //加载失败的资源
							tagName: event.target.tagName, //标签名
						});
					}
				},
				true
			); ////由于网络请求不会冒泡,必须在捕获阶段处理
		</script>

		<img src="http://127.0.0.1:3000/test.png" />
		<script src="http://127.0.0.1:3000/test2.js" />
	</body>

4.unhandledrejection,rejectionhandled

  • unhandledrejection:当 Promisereject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件
window.addEventListener("unhandledrejection", function (e) {
  //阻断异常继续抛出
  e.preventDefault();
  console.log("unhandledrejection捕获到promise错误的原因是", e.reason);
  console.log("Promise 对象是", e.promise);
  return true;
});

new Promise((resolve, reject) => {
  reject("promise error1");
});

new Promise((resolve) => {
  resolve();
}).then(() => {
  throw new Error("promise error2");
});

try {
  new Promise((resolve) => {
    resolve();
  }).then(() => {
    throw new Error("promise error3");
  });
} catch (e) {
  console.log("try catch:", e);
}

打印结果如下:

一文弄懂JavaScript的错误类型,捕获及上报

  • rejectionhandled:当Promiserejectedrejection处理器没有及时处理时会在全局触发rejectionhandled 事件
window.addEventListener("unhandledrejection", function (e) {
  //阻断异常继续抛出
  e.preventDefault();
  console.log("unhandledrejection捕获到promise错误的原因是:", e.reason);
  console.log("unhandledrejection Promise 对象是:", e.promise);
  return true;
});

//promise异常被处理了
window.addEventListener("rejectionhandled", (e) => {
  // rejected的原因
  console.log("rejectionhandled:", e.reason);
});

const p1 = new Promise((resolve, reject) => {
  reject("promise error1");
});

setTimeout(() => {
  p1.catch((e) => {
    console.log("catch捕获到promise1 错误:", e);
  });
}, 1000);

打印结果如下:

一文弄懂JavaScript的错误类型,捕获及上报

5.XMLHttpRequest、fetch、axios-网络请求

  • XMLHttpRequest:自己有onerror事件,可以自己封装一个库
  • fetch:自带catch
  • axios:也有完整的错误处理机制

6.ErrorBoundary-react异常

  • 子组件的渲染
  • 生命周期函数
  • 构造函数

7.errorHandler-vue异常

vue.config.errorHandler = (err, vm, info) {}

4.异常上报

1.发请求上报

axios.post(url, data);

但因为请求异步可被cancel,建议使用XMLHttpRequest发送同步请求

const syncReport = (url, { data = {}, headers = {} } = {}) => {
  const xhr = new XMLHttpRequest();
  xhr.open('POST', url, false);
  xhr.withCredentials = true;
  Object.keys(headers).forEach((key) => {
    xhr.setRequestHeader(key, headers[key]);
  });
  xhr.send(JSON.stringify(data));
};

2.sendBeacon

  • 数据发送可靠
  • 数据异步传输
  • 不影响下一个导航的载入
// 请求数据string,自动设置Content-Type 为 text/plain
const reportData = (url, data) => {
  navigator.sendBeacon(url, data);
};

// Blob一般手动设置其MIME type,一般设置为 application/x-www-form-urlencoded
const reportData = (url, data) => {
  const blob = new Blob([JSON.stringify(data), {
    type: 'application/x-www-form-urlencoded',
  }]);
  navigator.sendBeacon(url, blob);
};

// 可以直接创建一个新的Formdata,请求头自动设置Content-Type为multipart/form-data
const reportData = (url, data) => {
  const formData = new FormData();
  Object.keys(data).forEach((key) => {
    let value = data[key];
    if (typeof value !== 'string') {
      // formData只能append string 或 Blob
      value = JSON.stringify(value);
    }
    formData.append(key, value);
  });
  navigator.sendBeacon(url, formData);
};

我推荐使用 sendBeacon 发送数据,如不支持,则可以降级使用同步的 ajax 发送数据

3.gif图片

  • 图片src属性可以直接跨域访问
  • 相比PNG/JPGgif的体积可以达到最小,合法的GIF只需要43个字节
  • 一般采用1*1像素透明色来上报,不存储色彩空间数据,节约化
const reportData = (url, data) => {
  let img = document.createElement('img');
  const params = [];
  Object.keys(data).forEach((key) => {
    params.push(`${key}=${encodeURIComponent(data[key])}`);
  });
  img.onload = () => img = null;
  img.src = `${url}?${params.join('&')}`;
};

上报时机

可在页面加载卸载,tab、路由切换等时机都可上报

// 页面加载
window.addEventListener('load', reportData, false);
// 页面卸载
window.addEventListener('beforeunload', reportData, false);
// tab切换
document.addEventListener("visibilitychange", function() {
  if(document.visibilityState === 'visible') {
    reportData1();
  }
  if(document.visibilityState === 'hidden') {
    reportData2();
  }.
});