为啥要处理异常?
异常不可控,会影响最终的呈现效果,对异常的处理,主要作用有:
- 增加用户体验
- 远程定位问题
- 绸缪,及早发现问题
- 完善前端方案,前端监控系统
异常捕获的类型有以下7种:
- JS语法错误、代码异常
- AJAX请求异常
- 静态资源加载异常
Promise
异常Iframe
异常- 跨域
Script error
- 崩溃和卡顿
而捕获的js类型错误也分为7种:
Error
★ (很少见,基本都是开发人员自定义的)EvalError
RangeError
ReferenceError
★SyntaxError
(语法错误)★TypeError
★ (常见,类型不对)URIError
(很少见,仅在encodeURI和decodeURI方法中见到)
处理JS报错的常用方法?
try-catch-finally
- try-catch只能捕获到同步的运行时错误,对语法和异步错误缺捕获不到
try {
let name = 'jartto';
console.log(nam);
} catch(e) {
console.log('捕获到异常:',e);
}
输出
捕获到异常:ReferenceError: nam is not defined at <anonymous>:3:15
- 不能捕获到具体的语法错误,只有一个语法提示,对上一个例子改造一下,删除一个单引号
try {
let name = 'jartto;
console.log(nam);
} catch(e) {
console.log('捕获到异常:',e);
}
输出
捕获到异常:Uncaught SyntaxError: Invalid or unexpected token
- 不能捕获异步代码中的错误
try {
setTimeout(() => {
undefined.map(v => v);
}, 1000)
} catch(e) {
console.log('捕获到异常:',e);
}
输出
Uncaught TypeError: Cannot read property 'map'ofundefined at setTimeout (<anonymous>:3:11)
window.onerror
当JS运行时错误发生,window会触发一个ErrorEvent 接口的error事件,并执行window.onerror()
,使用语法为:
/**
* @param {String} message 错误信息
* @param {String} source 出错文件
* @param {Number} lineno 行号
* @param {Number} colno 列号
* @param {Object} error Error对象(对象)
*/
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:',{message, source, lineno, colno, error});
}
- 捕获同步运行的报错
Jartto;
捕获到异常:
{ message:"Uncaught ReferenceError:Jartto is not defined",
source:"http://127.0.0.1:8001/",
lineno:35,
colno:1
}
- 捕获语法错误,不能捕获到具体信息
let name = 'Jartto;
捕获到异常:
Uncaught SyntaxError: Invalid or unexpected token
- 异步运行错误
setTimeout(() => {
Jartto;
});
捕获到异常:
{
message: "Uncaught ReferenceError: Jartto is not defined",
source: "http://127.0.0.1:8001/",
lineno: 36,
colno: 5,
error: ReferenceError: Jartto is not defined at setTimeout (http://127.0.0.1:8001/:36:5)
}
- 网络请求 - 捕获不到
<img src="./jartto.png">
使用总结:不论是静态资源异常,或者接口异常,错误都无法捕获到。
在实际的使用过程中,onerror 主要是来捕获预料之外的错误,而 try-catch 则是用来在可预见情况下监控特定的错误,两者结合使用更加高效。
window.addEventListener
当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的onerror() 处理函数。这些 error 事件不会向上冒泡到 window ,能被单一的window.addEventListener 捕获,但是不同浏览器对于error事件的处理略有不同,需要特殊处理。
<scritp>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<img src="./jartto.png">
捕获到异常:
Event {isTrusted: true, type: 'error', target: img, currentTarget: window,...}
使用该方法需要注意:
- 不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。
- 需要注意避免 addEventListener 重复监听。
unhandledreject捕获Promise异常
现有项目中Promise基本上都用 async...await写法了,既然async...await使代码编写的更像是同步代码,当然可是可以使用try...catch这种方是去处理异常的,写个示例如下:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
// 这里捕获并处理所有在 'await' 语句中产生的错误
console.error('Error fetching data:', error.message);
}
}
fetchData();
但是对于于原生的Promise写法try...catch则就失效了,try...catch
是一种同步的错误处理机制。它允许你在一段代码块(try
块)中捕获执行时可能发生的错误。如果在 try
块中抛出了异常,控制权将立即转移到相应的 catch
块,其中可以处理或记录错误。如果没有 catch
块处理异常,或者异常在 catch
块中未被捕获,则程序会终止。
try {
// 可能抛出错误的代码 throw new Error("Something went wrong");
} catch (error) {
// 错误处理代码 console.error(error.message);
}
Promise
是用于处理异步操作的。当一个 Promise
成功(resolved)或失败(rejected)时,你可以使用 .then()
或 .catch()
方法来处理结果或错误。.then()
方法通常用于处理成功的情况,而 .catch()
用于处理失败的情况。
new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
reject(new Error("Async operation failed"));
}, 1000);
}).then(result => {
// 成功时的处理
console.log(result);
}).catch(error => {
// 失败时的处理
console.error(error.message);
});
总结:try...catch
语句本身不能直接捕获由 Promise
链中产生的错误,这是因为 Promise
的异步特性。Promise
的 .then()
和 .catch()
方法是在 Promise
结果可用或拒绝时异步调用的,而 try...catch
只能捕获同步代码中的错误。
但是,你可以通过将 Promise
的创建和调用放在 try...catch
块内,来捕获那些在 Promise
构造函数中抛出的错误,或者在 then
方法中同步抛出的错误。然而,这种方式并不能捕获在 Promise
内部异步执行时抛出的错误,这些错误应该在 .catch()
中处理。
但是如果遇到了一个老项目,大部分都是缺少catch的残缺代码,如下:
newPromise((resolve, reject) => {
reject('jartto: promise error');
});
此时为项目中每一处都加上catch则不太现实,这个时候,unhandledreject
就派上用场了,即使项目中Promise全部加上了catch处理,为了防止有漏掉的Promise异常,也建议增加一个全局的对unhandledreject监听,
window.addEventListener("unhandledrejection", function(e){
e.preventDefault()
console.log('捕获到异常:', e);
return true;
});
Promise.reject('promise error');
备注:e.preventDefault()
可阻止异常消息在控制台上的显示
参考文章:
react异常捕获
一个非常优秀的库:react-error-boundary(会被最近的错误边界组件捕获)
核心原理:采用react v16版本后自带的error boundary,通过静态方法getDerivedStateFromError和生命周期componentDidCatch方法完成错误的捕获和上报
不足之处:仅能捕获子组件在渲染期间的同步错误
无法捕获:父组件本身的错误、异步错误、各种合成事件中的错误、服务端渲染的错误、函数式组件无法定义Error Boundary,不过可以使用
测试用例:act
-
react事件回调中发生的错误无法被ErrorBoundary捕获的原因
事件回调并不属于react工作流程:render阶段,即「组件render」、「Diff算法」发生的阶段,commit阶段,即「渲染DOM」、「componentDidMount/Update执行」阶段
参考文章:
vue异常捕获
在vue中,没有直接与React的Error Boundary对标的组件处理方法,但是提供了两种钩子函数errorCaptured
和onErrorCaptured
,用来捕获渲染过程中的错误,并且可以作为错误边界来防止一个组件的错误导致整个应用的崩溃。
errorCaptured
errorCaptured
钩子函数可以在任何组件中定义,它接收三个参数:
err
- 发生的错误对象。vm
- 发生错误的 Vue 实例。info
- 错误的来源信息,比如是来自生命周期钩子还是异步更新队列。
export default {
name: 'App',
errorCaptured(err, vm, info) {
console.error(`An error occurred during ${info}: `, err);
// 返回 false 以让错误继续传播
return false;
},
}
该钩子函数应该返回一个布尔值。如果返回 true
,则表示错误已被处理,不会再向上抛出。如果返回 false
或者抛出一个新的错误,则会继续向上传播错误。
onErrorCaptured
onErrorCaptured
与errorCaptured
类似,使用方法也基本一致,该方法由vue3引入,使用方法如下:
import { onMounted, onErrorCaptured } from 'vue';
export default {
setup() {
onMounted(() => {
// 组件挂载后的操作
});
onErrorCaptured((err, instance, info) => {
console.error(`An error occurred during ${info}: `, err);
return false;
});
}
}
vue还提供了另外一种处理运行时错误的方法:errorHandler
,该方法和上述两种方法有着明显的区别
errorHandler
errorHandler
errorHandler
是一个全局的错误处理器,可以通过 Vue 的配置对象来设置。当 Vue 应用中的任何地方出现错误时,无论是在组件内还是在其他地方,errorHandler
都会被调用。这意味着它可以捕获所有类型的错误,包括那些在组件的生命周期钩子、异步更新队列、watcher、计算属性或模板渲染过程中发生的错误。使用方法如下:
Vue.config.errorHandler = function (err, vm, info) {
// err 是错误对象
// vm 是发生错误的 Vue 实例
// info 是错误的来源信息
console.error('Global error handler:', err, info); };
errorCaptured
errorCaptured
是一个组件级别的生命周期钩子,只能在组件内部定义。它会在组件渲染和观察期间捕获子组件树内的错误。当一个子组件或其子树中的组件抛出错误时,错误会被最近的具有 errorCaptured
钩子的祖先组件捕获。如果当前组件没有处理错误,错误将继续向上冒泡,直到被更高层级的 errorCaptured
处理,或者最终被全局的 errorHandler
处理。
export default {
errorCaptured: function (err, vm, info) {
// err 是错误对象
// vm 是发生错误的 Vue 实例
// info 是错误的来源信息
console.error('Component error captured:', err, info);
// 返回 true 表示错误被处理了,不会向上冒泡
return true;
}
};
onErrorCaptured
import { onErrorCaptured } from 'vue';
export default {
setup() {
onErrorCaptured((err, instance, info) => {
// err 是错误对象
// instance 是发生错误的 Vue 实例
// info 是错误的来源信息
console.error('Component error captured:', err, info);
// 返回 true 表示错误被处理了,不会向上冒泡
return true;
});
}
};
主要区别
-
作用范围
errorHandler
是全局的,可以捕获应用中的所有错误。errorCaptured
是局部的,仅捕获其子组件树中的错误。
-
错误处理
errorHandler
不会阻止错误冒泡,一旦发生错误,它总是会被调用。errorCaptured
允许组件处理错误并在必要时阻止错误冒泡。
-
调用顺序:
- 当一个错误发生时,
errorCaptured
首先被调用,如果错误没有被处理(即errorCaptured
返回false
或抛出新的错误),那么错误会继续传播,最终到达errorHandler
。
- 当一个错误发生时,
总之,errorHandler
更适合于全局错误处理和日志记录,而 errorCaptured
则更适合于局部错误处理和错误恢复逻辑。在构建复杂的应用时,两者结合使用可以提供更全面的错误管理策略。
监控平台的开发和接入
参考文章: