前端如何异常处理

一、为什么要处理异常

错误处理是程序设计时必须要考虑的问题。

执行JavaScript代码的时候,有些情况下会发生错误。因为Js 是单线程,当脚本抛出了未捕获的异常会使得这个脚本挂起,后面的代码无法执行,所以很多情况需要捕获不致命的异常,让代码继续执行。

错误分两种,一种是程序写的逻辑不对,导致代码执行异常。这类错误在项目引入typescript之后即可检查出来,所以项目建议尽量引入typescript以规避一些基础的问题。

另一种,是执行过程中,程序可能遇到无法预测的异常情况而报错,例如,接口请求异常,网络异常,无权限等。

捕获异常,并上报异常监控平台,以及合理的异常处理,不仅能提升用户体验,也可使技术及时发现错误,并帮助分析、排查、跟踪和解决问题。

对于这些错误,我们需要做如下处理:

  1. 捕获:捕获异常。

  2. 处理:

    1. 技术侧评估,错误是否是致命的,会不会导致其它连带错误,是否需要做后续处理
    2. 技术侧:一般错误都需要上报到异常监控平台(常用的异常上报监控平台如Sentry),以便技术发现、排查和跟踪问题
    3. 和产品确认:是不是需要将错误信息反馈给用户,提示用户如何处理该错误:如接口超时等

二、需要处理哪些异常

  • 异步任务异常

    • Promise异常
    • 接口请求异常:fetch,axios等,服务端报错,请求超时等
  • js语法错误,代码异常

  • 静态资源加载异常:主要有图片、script、css、font等资源的加载错误问题

  • 崩溃卡顿

三、如何捕获异常

建议接入Sentry上报系统,做异常监控处理。

四、常见的异常及其处理

Promise异常处理

promise 中使用 catch 可以非常方便的捕获到异步 error 。或者使用监听 unhandledrejection 来全局捕获这类异常。

注意:Promise 中的异常不能被 try-catch 和 window.onerror 捕获!(易错点!)

原因是,Promise 在执行回调中都用 try catch 包裹起来了,其中所有的异常都被内部捕获到了,并未往上抛异常。

javascript 复制代码
// 方法一:
new Promise((resolve, reject) => {
  reject('jartto: promise error');
}).catch(e=>{
  console.log(e);
  // 异常上报等异常处理
});

// 方法二:
promiseObj.then(undefined, (err)=>{
    catch_statements
});

// 错误示范: Promise异常无法被try catch捕获
async function run() {
  try {
    Promise.reject(new Error("Oops1!"));
  } catch (error) {
    error.message;
  }
}
run();

为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的兜底监听,用来全局监听Uncaught Promise Error

  • 注意: 使用Sentry上报时,Sentry会自行收集此类异常。如果自己再全局上报一次会导致重复上报。这里如果需要搜集额外信息,注意区分一下,否则可以直接用Sentry默认的上报即可。
javascript 复制代码
window.addEventListener("unhandledrejection", function(e){
  console.log(e);
  // 异常上报等异常处理
});

接口请求异常

现在接口请求一般都用fetch或axios。可以分别:

  1. fetch拦截
  2. axios请求/响应拦截器
  3. 使用async/await + try catch结合的方式, 或者使用promise.catch
复制代码

try- catch

能被 try catch 捕捉到的异常,必须是在报错的时候,线程执行已经进入 try catch 代码块,且处在 try catch 里面,这个时候才能被捕捉到。 如果是在之前,或者之后,都无法捕捉异常。

所以try catch能捕获捉到运行时非异步错误,无法捕获语法错误和异步错误。适用于命令式代码,不适用于声明式如React等(React捕获异常后续会说明)。

try catch 不能捕获的js异常:

  • 语法错误
  • 普通异步任务如setTimeout
  • Promise任务
  • async任务需要await才能捕获

例子:

csharp 复制代码
try {
   try_statements
}
[catch (exception) {
   catch_statements
}]
[finally {
   finally_statements
}]

全局js异常

方法一: window.onerror

监听 js 运行时错误事件,无法捕获资源加载异常。

window.onerror 捕获异常能力比 try-catch 稍微强点,无论是异步还是非异步错误,onerror 都能捕获到运行时错误。

ini 复制代码
window.onerror = function (message, source, lineno, colno, error) { }

不能捕获的异常:

  1. 能捕获所有同步执行错误。
  2. 能捕获普通异步任务错误。
  3. 不能捕获语法错误。
  4. 不能捕获Promise任务错误。
  5. 不能捕获async任务错误。
  6. 不能捕获资源加载错误。

方法二: window.addEventListener('error')

监听 js 运行时错误事件,可以捕获资源加载异常。但错误堆栈不完整。

javascript 复制代码
window.addEventListener('error', (error) => {
      console.log('捕获到异常:', error);
    }, true)

异步代码

使用async/await + try/catch

这里也可以使用相关babel插件,来给所有的async函数统一添加try/catch。感兴趣的同学可以了解下。

javascript 复制代码
async function test() {
  try {
    const res = await requestWithTimeout(fakeAPI(), 0);
  } catch (e) {
    console.log("~~~e", e.message);
    // 异常
  }
}

iframe异常

分三种情况:

  1. iframe 页面和你的主站是同域名:直接给 iframe 添加 onerror 事件即可。
  2. iframe 页面和你的主站不是同个域名,但是自己可以控制,可以通过与 iframe 通信的方式将异常信息抛给主站接收。与 iframe 通信的方式有很多,常用的如:postMessage,hash 或者 name 字段跨域等
  3. iframe 页面和你的主站不是同个域名,且网站不受自己控制为第三方的话,没办法捕获,这是出于安全性的考虑,只能通过控制台看到详细的错误信息

静态资源加载异常

方法一:资源标签onerror 属性来捕获

ini 复制代码
function errorHandler(error) {
    console.log("捕获到静态资源加载异常", error);
  }
  
  <script src="http://**/js/test.js" onerror="errorHandler(this)"></script>
  <link rel="stylesheet" href="http://***/test.css" onerror="errorHandler(this)"/>

这样可以拿到静态资源的错误,但缺点很明显,代码的侵入性太强了,每一个静态资源标签都要加上 onerror 方法。

是否也可以通过 window.onerror 去全局监听加载失败呢?答案是否定的。因为 onerror 的事件并不会向上冒泡,window.onerror 接收不到加载失败的错误。只能通过该资源标签的onerror 方法,才可以。

冒泡不行,但是捕获阶段可以,也即addEventListener("error"),下面的方法二。

方法二:addEventListener("error")

我们可以通过捕获的方式全局监控加载失败的错误,虽然这也监控到了脚本错误,但通过 !(event instanceof ErrorEvent) 判断便可以筛选出加载失败的错误

javascript 复制代码
window.addEventListener('error', (error) => {
      console.log('捕获到异常:', error);
    }, true)

页面崩溃异常

javascript 复制代码
window.addEventListener('load',()=>{
    sessionStorage.setTitem('page_exit','pending')
})
window.addEventListener('beforeunload',()=>{
    sessionStorage.setTitem('page_exit','true')
})

sessionStorage.getTitem('page_exit')!='true' // 页面崩溃

框架的异常处理

vue全局异常处理

主站已经加了,所以这里不用担心。

javascript 复制代码
Vue.config.errorHandler = (err, vm, info) => {
  console.error('通过vue errorHandler捕获的错误');
  console.error(err);
  console.error(vm);
  console.error(info);
}

React异常处理

React 16 提供了一个内置函数 componentDidCatch,使用它可以非常简单的获取到 react 下的错误信息

javascript 复制代码
componentDidCatch(error, info) {
    console.log(error, info);
}

Error Boundary

UI 的某部分引起的 JS 错误不应该拖垮整个应用,React 16 引入了一种关于错误边界(error boundary)的理念。

注意:error boundaries 不会捕捉下面这些错误:

  1. .事件处理器:如按钮点击事件等
  2. 异步代码
  3. 服务端的渲染代码
  4. error boundaries 区域内的错误
## 不同环境的异常处理

Node中:

dart 复制代码
process.on('uncaughtException', () => {})`
相关推荐
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
韩楚风5 小时前
【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
linux·服务器·性能优化·架构·gnu
yanlele8 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
_.Switch10 小时前
Python机器学习:自然语言处理、计算机视觉与强化学习
python·机器学习·计算机视觉·自然语言处理·架构·tensorflow·scikit-learn
feng_xiaoshi14 小时前
【云原生】云原生架构的反模式
云原生·架构
架构师吕师傅16 小时前
性能优化实战(三):缓存为王-面向缓存的设计
后端·微服务·架构
团儿.17 小时前
解锁MySQL高可用新境界:深入探索MHA架构的无限魅力与实战部署
数据库·mysql·架构·mysql之mha架构
艾伦~耶格尔1 天前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
_.Switch1 天前
Python机器学习框架介绍和入门案例:Scikit-learn、TensorFlow与Keras、PyTorch
python·机器学习·架构·tensorflow·keras·scikit-learn
神一样的老师2 天前
构建5G-TSN测试平台:架构与挑战
5g·架构