程序发生了意想不到的情况,这种情况影响到了程序的正确运行
下列代码不会产生异常
function func() {
console.log(1);
new Error("this is an error");
console.log(2);
}
异常分为: 编译时异常和运行时异常
JS 中如果程序发生了运行时异常,会沿着调用栈一层层向上传播,直到栈空。
在实现一个基础工具库时,我们可能需要确保调用者传入的参数满足一定的条件。如果参数不符合预期,我们可以主动抛出异常来提醒调用者。
考虑一个简单的工具函数,用于计算两个数的除法:
function divideNumbers(a, b) {
// 检查除数是否为零
if (b === 0) {
// 如果除数为零,主动抛出异常
throw new Error("除数不能为零");
}
return a / b;
}
// 调用示例
try {
const result = divideNumbers(10, 2);
console.log("结果:", result);
const resultWithZeroDenominator = divideNumbers(8, 0); // 这里会抛出异常
console.log("不会执行到这里"); // 由于异常,这行代码不会执行
} catch (error) {
console.error("发生错误:", error.message); // 捕获并处理异常
}
对于复杂的业务逻辑,我们可能需要抛出不同类型的异常,以便调用者能够根据异常类型来进行不同的处理。在 JavaScript 中,我们可以通过继承 Error 类来实现自定义异常类型。
class BusinessError extends Error {
constructor(message) {
super(message);
this.name = "BusinessError"; // 设置异常的名称
}
}
作为调用方:在异常处理的上下文中,"处理或继续抛出"意味着在捕获到异常后,开发者可以选择在当前位置进行处理(即提供一个解决方案或记录错误日志等),也可以选择将异常继续抛出,让上层调用者或全局错误处理程序来处理这个异常。
function processUserInput(value) {
try {
// 模拟对用户输入进行处理的业务逻辑
if (typeof value !== "number") {
throw new TypeError("输入必须是数字"); // 主动抛出类型错误异常
}
// 业务逻辑处理...
return value * 2;
} catch (error) {
// 处理异常或继续抛出
if (error instanceof TypeError) {
console.error("类型错误:", error.message);
// 可以选择在这里提供一个默认值或其他补救措施
return 0;
} else {
// 如果不是类型错误,将异常继续抛出
throw error;
}
}
}
// 调用示例
try {
const result = processUserInput("abc");
console.log("结果:", result);
} catch (error) {
console.error("发生错误:", error.message);
}
PS: 切记不要捕获了异常却不处理,这样会导致异常被吞掉,从而影响程序的正确运行。
try-catch 捕获异常的时候只能够捕获同步运行时的错误和异常,不能捕获语法错误和异步执行代码的错误和异常。
try {
throw new Error("error");
} catch (error) {
console.log(error);
}
如果包了一层 setTimeout,情况就不一样了
try {
setTimeout(() => {
throw new Error("error");
}, 1000);
} catch (error) {
console.log(error);
}
此时我们的 try-catch 无法捕获到异常,异常向上抛出,最终被浏览器捕获,浏览器会打印出Uncatch Error
的异常信息。
window.onerror 捕获异常能力比 try-catch 稍微强点,无论是异步还是非异步错误,onerror 都能捕获到运行时错误
window.onerror 有一个返回值,如果返回 true,浏览器就不会再处理这个错误,如果返回 false,浏览器会继续处理这个错误,这个返回值的作用是阻止浏览器默认的错误处理行为。
window.onerror = function (message, source, lineno, colno, error) {
console.error("全局错误捕获:", message, source, lineno, colno, error);
// 可以在这里进行错误的上报等操作
return true; // 阻止浏览器默认的错误处理行为
};
// 模拟一个异步代码块中的错误
setTimeout(function () {
// 这里故意引发一个错误
undefinedFunction(); // undefinedFunction is not defined
}, 1000);
// 模拟一个同步代码块中的错误
try {
throw new Error("手动抛出一个错误");
} catch (error) {
console.error("捕获同步错误:", error.message);
}
关于 window.onerror 还有两点需要注意:
1. 全局捕获的代码最好放在所有代码的最前面,比如在 html 中嵌入一个 script 标签,这样才能捕获到所有的错误
比如笔者的项目中曾这样使用
<script>
function report(errorMsg) {
var reportUrl = "/error";
errorMsg.unshift(["userAgent", navigator.userAgent]);
var params = errorMsg.reduce(function (
accumulator,
currentValue,
currentIndex
) {
var connectSymbol = "&";
if (currentIndex === 0) {
connectSymbol = "";
}
return (
accumulator + connectSymbol + currentValue[0] + "=" + currentValue[1]
);
},
"?");
new Image().src = reportUrl + params;
}
window.onerror = function (msg, url, lineNo, columnNo, error) {
var errorMsg = [
["msg", msg],
["src", url],
["error", JSON.stringify(error)],
];
report(errorMsg);
};
window.addEventListener("unhandledrejection", function (e) {
e.preventDefault();
var errorMsg = [
["msg", "unhandled rejection"],
["reason", JSON.stringify(e.reason)],
];
report(errorMsg);
});
</script>
2. window.onerror 是无法捕获到网络异常的错误
Promise 异常处理主要通过 Promise 的 .catch() 方法来进行。 此外,对于未被处理的 Promise rejection,可以使用 unhandledrejection 事件进行全局的异常处理。
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作,这里故意抛出一个错误
setTimeout(() => {
reject(new Error("Promise 异常"));
}, 1000);
});
myPromise
.then((result) => {
console.log("Promise 成功:", result);
})
.catch((error) => {
console.error("Promise 异常捕获:", error.message);
});
// unhandledrejection 全局异常处理
window.addEventListener("unhandledrejection", (event) => {
console.error("未处理的 Promise rejection:", event.reason);
// 可以在这里进行错误的上报等操作
});
// 模拟未被处理的 Promise rejection
const unhandledRejectionPromise = new Promise((resolve, reject) => {
// 模拟异步操作,这里故意抛出一个错误
setTimeout(() => {
reject(new Error("未处理的 Promise rejection"));
}, 2000);
});
运行结果如下:
因为我们在线上的版本,经常做静态资源 CDN 化,这就会导致我们常访问的⻚面跟脚本文件来自不同的域名,这时候如果没有进行额外的配置,就会容易产生 Script error。
如果出现 Script error 这样的错误,基本上可以确定是出现了跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下:
跨源资源共享机制( CORS ):我们为 script 标签添加 crossOrigin 属性
<script src="http://example.com/external-script.js" crossorigin></script>
或者动态去添加 js 脚本:
const script = document.createElement("script");
script.src = "http://example.com/external-script.js";
script.crossOrigin = "anonymous";
document.body.appendChild(script);
特别注意,服务器端需要设置:Access-Control-Allow-Origin
import React, { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, errorInfo) {
this.setState({ hasError: true });
// 在这里可以进行错误日志的上报
console.error("捕获到React错误:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>发生错误,组件渲染失败。</h1>;
}
return this.props.children;
}
}
// 在组件中使用错误边界
class MyComponent extends Component {
render() {
return (
<ErrorBoundary>
{/* 此处放置可能出现错误的组件 */}
<ChildComponent />
</ErrorBoundary>
);
}
}
在 Vue 中,可以通过全局错误处理函数、组件的 errorCaptured 钩子以及一些插件来进行异常处理。
// 全局错误处理函数
Vue.config.errorHandler = function (err, vm, info) {
// 在这里可以进行错误日志的上报
console.error('捕获到Vue错误:', err, info);
};
// 组件中使用 errorCaptured 钩子
Vue.component('my-component', {
errorCaptured(err, vm, info) {
// 在这里可以进行组件内部错误的处理
console.error('组件内部错误:', err, info);
return false; // 阻止错误继续冒泡到全局错误处理函数
},
// 组件的其他配置...
});
两种主流上报方式:
- 通过 Ajax 发送数据因为 Ajax 请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建 img 标签的形式进行上报。
- 动态创建 img 标签的形式