认识
一、代码执行异常
1.1 错误类型
-
Error
: 最基本的错误类型,其他的错误类型都继承自该类型。通过Error
,我们可以自定义Error
类型。 -
RangeError
: 范围错误。当出现堆栈溢出(递归没有终止条件)、数值超出范围(new Array
传入负数或者一个特别大的整数)情况时会抛出这个异常。 -
ReferenceError
: 引用错误。当一个不存在的对象被引用时发生的异常。 -
SyntaxError
: 语法错误。如变量以数字开头;花括号没有闭合等。 -
TypeError
: 类型错误。如把number
当str
使用。 -
URIError
: 向全局URI
处理函数传递一个不合法的URI
时,就会抛出这个异常。如使用decodeURI('%')
、decodeURIComponent('%')
。 -
EvalError
: 一个关于eval
的异常,不会被javascript
抛出。
1.2 try catch
try{}catch(){}
能捕捉到的异常必须是线程执行已经进入 try catch
但 try catch
未执行完的时候抛出来的。当程序运行到 try catch
里面时,如果未报错,则忽略 catch
中的代码,若报错,则不执行 try
报错内容后面的代码,转而执行 catch
中的代码。try{}catch(){}
有如下特点:
-
无法捕获语法错误: 因为语法错误是在语法检查阶段就报错了,线程执行尚未进入
try catch
代码块,自然就无法捕获到异常。 -
无法捕获异步错误: 因为等异步函数里面的事件进入事件队列的时候,主线程已经离开了
try catch
,所以try catch
是无法捕获异步函数的错误的。 -
无法捕获
Promise
异常: 当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在try-catch
所在的栈,所以try-catch
并不能捕获到错误。
语法
// 示例1:常规运行时错误,可以捕获 ✅
try {
let a = undefined;
if (a.length) {
console.log('111');
}
} catch (e) {
console.log('捕获到异常:', e);
}
// 示例2:语法错误,不能捕获 ❌
try {
const notdefined,
} catch(e) {
console.log('捕获不到异常:', 'Uncaught SyntaxError');
}
// 示例3:异步错误,不能捕获 ❌
try {
setTimeout(() => {
console.log(notdefined);
}, 0)
} catch(e) {
console.log('捕获不到异常:', 'Uncaught ReferenceError');
}
1.3 window.onerror
window.onerror
事件在冒泡阶段执行, 用于全局捕获错误。 window.onerror
得到了五个参数,获取的报错堆栈相对很完整。 window.onerror
可以捕获异步错误; 捕获不到静态资源加载异常的错误,原因是资源类型错误没有冒泡,只能在捕获阶段捕获,而 window.onerror
是通过在冒泡阶段捕获错误,对静态资源加载类型异常无效,所以只能借助 window.addEventListener('error', callback, true)
的方式捕获。
语法
window.onerror = function(message, source, lineno, colno, error) { ... }
-
message
:错误信息(字符串)。可用于HTMLonerror
=处理程序中的event
。 -
source
:发生错误的脚本URL
(字符串) -
lineno
:发生错误的行号(数字) -
colno
:发生错误的列号(数字) -
error
:Error
对象
示例
window.onerror = function(message, source, lineno, colno, error) {
console.log("捕获到的错误信息是:", message, source, lineno, colno, error);
};
// 示例1:常规运行时错误,可以捕获 ✅
console.log(notdefined);
// 示例2:语法错误,不能捕获 ❌
const notdefined;
// 示例3:异步错误,可以捕获 ✅
setTimeout(() => {
console.log(notdefined);
}, 0);
// 示例4:资源错误,不能捕获 ❌
let script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://www.test.com/index.js";
document.body.appendChild(script);
1.4 window.addEventListener("error")
window.addEventListener('error', true)
用于在捕获阶段全局捕获错误。可以捕获异步错误、可以捕获网络异常、静态资源加载异常错误。但是通过 window.addEventListener('error', true)
获取的报错堆栈不是很完整。
window.addEventListener("error", function (event) {
const { error } = event;
console.log("error: ", error.message);
console.log("error: ", error.stack);
});
二、Promise 异常
2.1 promise().catch()
Promise.reject(); // Uncaught (in promise) undefined
2.2 window.onrejectionhandled
当Promise
被reject
并且错误信息已经被catch
处理的时候,这个错误不会被window.onerror
以及window.addEventListener("error",handler,true)
捕获。但是有专门的rejectionhandled
事件方法进行捕获处理
语法
Promise.reject(); // Uncaught (in promise) undefined
2.3 window.onunhandledrejection
当Promise
被reject
并且错误信息没有被catch
处理的时候,这个错误不会被window.onerror
以及window.addEventListener("error",handler,true)
捕获。但是有专门的unhandledrejection
事件方法进行捕获处理
语法
Promise.reject(); // Uncaught (in promise) undefined
2.4 window.addEventListener("rejectionhandled")
当Promise
被reject
并且错误 信息已经被catch
处理的时候,这个错误不会被window.onerror
以及window.addEventListener("error",handler,true)
捕获。但是有专门的rejectionhandled
事件方法进行捕获处理
语法
window.addEventListener("rejectionhandled", event => {
console.log("Promise rejected; reason: " + event.reason);
}, false);
window.onrejectionhandled = ()=>{
}
Promise.reject(throw new Error("xxx")).catch(error=> { console.log(error) });
2.5 window.addEventListener("unhandledrejection")
当Promise
被reject
并且错误信息没有被catch
处理的时候,这个错误不会被window.onerror
以及window.addEventListener("error",handler,true)
捕获。但是有专门的unhandledrejection
事件方法进行捕获处理
语法
window.addEventListener("unhandledrejection", event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
window.onunhandledrejection = event => {
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
};
Promise.reject(throw new Error("xxx"));
三、网络请求异常
在浏览器端发起一个接口请求时,如果请求的 url
的有问题,也会抛出异常。
3.1 xhr.onerror
如果是 xhr.send
方法执行时出现异常,可以通过 xhr.onerror
或者 xhr.addEventListener('error', callback)
的方式捕获异常。
xhr.open('get', '/user/userInfo');
xhr.send(); // send localhost:3000/user/userinfo net::ERR_FAILED
3.2 xhr.addEventListener('error',callback)
如果是 xhr.send
方法执行时出现异常,可以通过 xhr.onerror
或者 xhr.addEventListener('error', callback)
的方式捕获异常。
xhr.open('get', '/user/userInfo');
xhr.send(); // send localhost:3000/user/userinfo net::ERR_FAILED
3.3 window.onerror
如果是 xhr.open
方法执行时出现异常,可以通过 window.addEventListener('error', callback)
或者 window.onerror
的方式捕获异常。
xhr.open('GET', "https://") // Uncaught DOMException: Failed to execute 'open' on 'XMLHttpRequest': Invalid URL
at ....
3.4 window.addEventListener('error', callback)
如果是 xhr.open
方法执行时出现异常,可以通过 window.addEventListener('error', callback)
或者 window.onerror
的方式捕获异常。
xhr.open('GET', "https://") // Uncaught DOMException: Failed to execute 'open' on 'XMLHttpRequest': Invalid URL
at ....
3.5 fetch(url).then(callback).catch(callback)
接口调用是通过 fetch
发起的, 我们可以通过 fetch(url).then(callback).catch(callback)
的方式去捕获异常。
四、静态资源加载异常
有的时候,如果我们页面的img
、js
、css
等资源链接失效,就会提示资源类型加载如异常。
4.1 xxx.onerror
img.onerror = function(){};
link.onerror = function(){};
script.onerror = function(){};
4.2 window.addEventListener('error', callback, true)
针对这一类的异常,我们可以通过 window.addEventListener('error', callback, true)
的方式进行全局捕获。这里要注意一点,使用 window.onerror = callback
的方式是无法捕获静态资源类异常的。原因是资源类型错误没有冒泡,只能在捕获阶段捕获,而 window.onerror
是通过在冒泡阶段捕获错误,对静态资源加载类型异常无效,所以只能借助 window.addEventListener('error', callback, true)
的方式捕获 。
<img src="localhost:3000/data.png" /> // Get localhost:3000/data.png net::ERR_FILE_NOT_FOUND
五、跨域脚本执行异常
当项目中引用的第三方脚本执行发生错误时,会抛出一类特殊的异常。这类型异常和我 们刚才讲过的异常都不同,它的 msg
只有 Script error
信息,没有具体的行、列、类型信息。
之以会这样,是因为浏览器的安全机制: 浏览器只允许同域下的脚本捕获具体异常信息,跨域脚本中的异常,不会报告错误的细节,这样可以有效避免敏感信息无意中被第三方(不受控制的)脚本捕获到。
5.1 window.addEventListener('error', callback)
针对这类型的异常,我们可以通过 window.addEventListener('error', callback)
或者 window.onerror
的方式捕获异常。当然,如果我们想获取这类异常的详情,需要做以下两个操作:
- 在发起请求的
script
标签上添加crossorigin="anonymous"
<script src="http://xxxxxxxx/texterror.js" crossorigin="anonymous"></script>
- 请求响应头中添加
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: *
这样就可以获取到跨域异常的细节信息了。
六、Vue 异常
Vue
项目中,vue
使用 try/catch
来捕获常规代码的报错,被捕获的错误会通过 console.error
输出而避免应用崩溃。 因此 window.onerror
和 error
事件不能捕获到常规的代码错误。如下所示:
异常代码
export default {
created() {
let a = null;
if (a.length > 1) {
// ...
}
}
};
main.js
中捕获代码
window.addEventListener('error', (error) => {
console.log('error', error);
});
window.onerror = function (msg, url, line, col, error) {
console.log('onerror', msg, url, line, col, error);
};
控制台会报错,但是 window.onerror
和 error
不能捕获到。
Vue
通过 Vue.config.errorHandler
或者 app.config.errorHandler
来捕获异常:
Vue.config.errorHandler = function (err, vm, info) {
// handle error
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
}
app.config.errorHandler = (err, instance, info) => {
// 处理错误,例如:报告给一个服务
}
七、React 异常
从 React16
开始,官方提供了 ErrorBoundary
错误边界的功能,被该组件包裹的子组件,render
函数报错时会触发离当前组件最近父组件的 ErrorBoundary
。生产环境,一旦被 ErrorBoundary
捕获的错误,也不会触发全局的 window.onerror
和 error
事件
父组件代码
import React from 'react';
import Child from './Child.js';
// window.onerror 不能捕获render函数的错误 ❌
window.onerror = function (err, msg, c, l) {
console.log('err', err, msg);
};
// error 不能render函数的错误 ❌
window.addEventListener(
'error',
(error) => {
console.log('捕获到异常:', error);
},
true
);
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// componentDidCatch 可以捕获render函数的错误
console.log(error, errorInfo);
// 同样可以将错误日志上报给服务器
reportError(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function Parent() {
return (
<div>
父组件
<ErrorBoundary>
<Child />
</ErrorBoundary>
</div>
);
}
export default Parent;
子组件代码
// 子组件 渲染出错
function Child() {
let list = {};
return (
<div>
子组件
{list.map((item, key) => (
<span key={key}>{item}</span>
))}
</div>
);
}
export default Child;