WEB前端异常处理-Part2:异常捕获

294 阅读7分钟

1:前言

继上一篇 异常类型 的介绍与简单处理方案,这一篇将继续深挖错误是如何被捕获的。

通过阅读这篇文章,你将了解到:

  1. 不同类型的错误:包括同步错误、异步错误、引用资源错误、资源不存在错误、DOM 相关错误和 Promise 错误。
  2. 各类错误捕获方法的适用场景:了解在不同场景下使用哪种捕获方法最为合适。
  3. 具体示例与实践:通过具体的代码示例,直观地理解各类错误捕获方法的使用方法和效果。

无论你是初学者还是有经验的开发者,这篇文章都将为你提供系统化的错误捕获知识,帮助你理解市面上众多的监控软件的运行逻辑。

文章中每个结论都有可验证实例绝非泛泛而谈,废话不说我将逐一介绍,请随我一起看下去!

2:try catch

2.1:同步错误

// 同步错误
try {
  var arr = []
  arr.length = -1
} catch (err) {
  console.log('err', err) // err RangeError: Invalid array length
}

try {
  b.length
} catch (err) {
  console.log('err', err) // err ReferenceError: b is not defined
}

try {
  var foo = undefined
  foo.substring(1)
} catch (err) {
  console.log('err', err) // err TypeError: Cannot read properties of undefined (reading 'substring')
}

console.log('start')
try {
  console.log('run')
  b.length
} catch (err) {
  console.log('err', err)
}
console.log('end')
start
run
err ReferenceError: b is not defined
end

通过上面的实例我们可以知道:try 块里的同步错误都被正确捕获并且不影响代码正常执行

2.2:异步错误

// 异步错误
var a = null
console.log('start')
try {
  setTimeout(() => {
    console.log('我是一个异步错误:', a.splice(','))
  })
} catch (err) {
  console.log('err', err)
}
console.log('end')

企业微信截图_17205082644762.png

对异步无能为力

2.3:promise错误

try {
  Promise.reject('promise error1')
  new Promise((resolve, reject) => {
    reject('promise error2')
  })
  new Promise((resolve) => {
    resolve()
  }).then(() => {
    throw 'promise error3'
  })
} catch (err) {
  console.log('err', err) // err RangeError: Invalid array length
}

image2024-6-4_14-26-54.png

对于promise也是无能为力

2.4:结论

try 块可以捕获同步错误且不影响代码正常执行流,但异步错误无能为力,同样的 promise 也是无能为力

3:window.onerror

3.1:同步错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Window.onerror Example</title>
  </head>
  <body>
    <script>
      // 定义全局错误处理函数
      /**
       * @param {String}  msg    错误信息
       * @param {String}  url    出错文件
       * @param {Number}  row    行号
       * @param {Number}  col    列号
       * @param {Object}  error  错误详细信息
       */
      window.onerror = function (msg, url, row, col, error) {
        console.log('我捕获了一个同步步错误', {
          msg,
          url,
          row,
          col,
          error
        })
        // 返回 true 表示错误已经被处理,不再向控制台输出
        return true
      }
      console.log('start')
      // 触发一个同步错误
      function aaa() {
        var arr = []
        arr.length = -1
      }
      aaa()
      console.log('end')
    </script>
  </body>
</html>

image2024-6-4_15-8-31.png

可以捕获同步错误,但很可惜的是js执行被无条件阻断,这一点不及 try catch

3.2:异步错误

// 异步错误
/**
 * @param {String}  msg    错误信息
 * @param {String}  url    出错文件
 * @param {Number}  row    行号
 * @param {Number}  col    列号
 * @param {Object}  error  错误详细信息
 */
console.log('start')
setTimeout(() => {
  let a = null
  console.log('我是一个异步错误:', a.splice(','))
})
console.log('end')
window.onerror = function (msg, url, row, col, error) {
  console.log('我捕获了一个异步错误', {
    msg,
    url,
    row,
    col,
    error
  })
  return true
}

image2024-6-3_17-54-43.png 运行上面的代码我们可以看出window.onerror貌似可以捕捉到异步错误,而且报错信息很全

3.3:引用资源的错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body></body>
  <script type="text/javascript">
    window.onerror = function (msg, url, row, col, error) {
      console.log('我捕获一个引用资源错误:', {
        msg,
        url,
        row,
        col,
        error
      })
      return true
    }
  </script>
  <script src="./outSideSource.js"></script>
</html>

// outSideSource.js
var arr = []
arr.length = -1

image2024-6-3_18-1-40.png

从上方可以看出window.onerror可以捕获一个引用资源的报错,但如果是一个异步的资源可以吗?

// outSideSource.js
setTimeout(() => {
  var arr = []
  arr.length = -1
}, 5000)

image2024-6-3_18-3-48.png

从上方可以看出window.onerror可以捕获一个异步的资源的报错,假设我们引用的资源不存在是否会被捕获?

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body></body>
  <script type="text/javascript">
    window.onerror = function (msg, url, row, col, error) {
      console.log('我捕获一个引用资源错误:', {
        msg,
        url,
        row,
        col,
        error
      })
      return true
    }
  </script>
  <script src="./abcd.js"></script>
</html>

image2024-6-3_18-6-50.png 可以看到对于不存在的资源确实无能为力

3.4:dom资源错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <script type="text/javascript">
    window.onerror = function (msg, url, row, col, error) {
      console.log('我捕获一个引用资源错误:', {
        msg,
        url,
        row,
        col,
        error
      })
      return true
    }
  </script>
  <body>
    <img src="./abcd.js" />
  </body>
</html>

image2024-6-4_17-7-51.png

可以看到对于dom上的错误也是无能为力

3.5:promise错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body></body>
  <script type="text/javascript">
    window.onerror = function (msg, url, row, col, error) {
      console.log('我捕获一个引用资源错误:', {
        msg,
        url,
        row,
        col,
        error
      })
      return true
    }
    Promise.reject('promise error1')
    new Promise((resolve, reject) => {
      reject('promise error2')
    })
    new Promise((resolve) => {
      resolve()
    }).then(() => {
      throw 'promise error3'
    })
  </script>
</html>

image2024-6-4_17-26-12.png

可以看到对于promise错误也是无能为力

3.6:结论

window.onerror可以捕获同步、异步、引用资源的内部错误。但是如果引用资源不存、dom上的错误、promise错误的捕获是无能为力的

4:window.addEventListener

4.1:同步错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Window.onerror Example</title>
  </head>
  <body>
    <script>
      window.addEventListener(
        'error',
        (error) => {
          console.log('我捕获一个同步错误:', error)
        },
        true
      )
      console.log('start')
      // 触发一个同步错误
      function aaa() {
        var arr = []
        arr.length = -1
      }
      aaa()
      console.log('end')
    </script>
  </body>
</html>

image2024-6-4_17-38-3.png

可以看出是可以捕获到同步错误

4.2:异步错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Window.onerror Example</title>
  </head>
  <body>
    <script>
      window.addEventListener(
        'error',
        (error) => {
          console.log('我捕获一个异步错误:', error)
        },
        true
      )
      console.log('start')
      // 触发一个同步错误
      setTimeout(() => {
        let a = null
        a.splice(',')
      })
      console.log('end')
    </script>
  </body>
</html>

image2024-6-4_17-35-37.png

可以看出是可以捕获到异步错误

4.3:引用资源的错误

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body></body>
  <script type="text/javascript">
    window.addEventListener(
      'error',
      (error) => {
        console.log('我捕获引用资源同步错误:', error)
      },
      true
    )
  </script>
  <script src="./outSideSource.js"></script>
</html>
// outSideSource.js
var arr = []
arr.length = -1

image2024-6-4_17-43-9.png 同步资源可以,我们试试异步资源报错

// outSideSource.js
setTimeout(() => {
  var arr = []
  arr.length = -1
}, 5000)

image2024-6-4_17-46-45.png

4.4:不存在资源的错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body></body>
  <script type="text/javascript">
    window.addEventListener(
      'error',
      (error) => {
        console.log('我捕获一个引用资源错误:', error)
      },
      true
    )
  </script>
  <script src="./abcd.js"></script>
</html>

image2024-6-4_13-53-57.png

可以看出对于资源不存在的情况也可以捕获

4.5:dom资源错误

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    <img src="./abcd.js" />
  </body>
  <script type="text/javascript">
    window.addEventListener(
      'error',
      (error) => {
        console.log('我捕获一个引用资源错误:', error)
      },
      true
    )
  </script>
</html>

image2024-6-4_13-55-34.png

以上可以看出Dom图片资源错误也可以捕捉,非常棒!

4.6:unhandledrejection的错误

window.addEventListener('unhandledrejection', function (e) {
  e.preventDefault()
  console.log('我知道 promise 的错误了:', e.reason)
  return true
})
Promise.reject('promise error1')
new Promise((resolve, reject) => {
  reject('promise error2')
})
new Promise((resolve) => {
  resolve()
}).then(() => {
  throw 'promise error3'
})
Promise.reject('promise error4').catch((e) => console.log(e))
new Promise((resolve, reject) => {
  reject('promise error5')
}).catch((e) => console.log(e))

image2024-6-4_18-10-59.png

以上可以看出unhandledrejection不能完全捕获reject

4.7:结论

window.addEventListener这个api很是强大,只有promise的部分reject不能正确捕获,其余的都可以正常捕获。

也可以告诉我们如果在监控系统中,最好不要去做catch动作,而是让这个问题无阻碍的暴露给捕获器。否则捕获器可能捕获不到已经出现的错误,导致关键信息丢失。

具体参见MDN

5:总结

5.1:先看一个图表

捕获方法同步错误异步错误引用资源错误资源不存在错误Dom相关错误promise错误
try_catchxxxxx
window.onerrorxxx
window.addEventListener√(部分)

5.2:结论

  1. try...catch

    • 同步错误:可以捕获同步代码中的错误。
    • 异步错误:无法捕获异步代码中的错误(如 setTimeoutsetInterval 中的错误)。
    • 引用资源错误:无法捕获引用资源(如脚本、样式表)加载错误。
    • 资源不存在错误:无法捕获资源加载失败(如 img 标签的地址不对)错误。
    • DOM相关错误:无法捕获 DOM 操作中的错误。
    • Promise错误:无法捕获未处理的 Promise 错误。
  2. window.onerror

    • 同步错误:可以捕获同步代码中的错误。
    • 异步错误:可以捕获异步代码中的错误(如 setTimeoutsetInterval 中的错误)。
    • 引用资源错误:可以捕获引用资源(如脚本、样式表)加载错误。
    • 资源不存在错误:无法捕获资源加载失败(如 img 标签的地址不对)错误。
    • DOM相关错误:无法捕获 DOM 操作中的错误。
    • Promise错误:无法捕获未处理的 Promise 错误。
  3. window.addEventListener ('error', ...) / ('unhandledrejection', ...)

    • 同步错误:可以通过 window.addEventListener('error', ...) 捕获同步代码中的错误。
    • 异步错误:可以通过 window.addEventListener('error', ...) 捕获异步代码中的错误。
    • 引用资源错误:可以通过 window.addEventListener('error', ...) 捕获引用资源(如脚本、样式表)加载错误。
    • 资源不存在错误:可以通过 window.addEventListener('error', ...) 捕获资源加载失败(如 img 标签的地址不对)错误。
    • DOM相关错误:可以通过 window.addEventListener('error', ...) 捕获 DOM 操作中的错误。
    • Promise错误:可以通过 window.addEventListener('unhandledrejection', ...) 捕获未处理的 Promise 错误。

以上是我们了解WEB错误捕获系统的基础知识体系。

借此我们可以大致明白市面上的监控系统的捕获动作是如何运行的,例如:Sentry、贝壳 LightHouse、友盟、阿里云 ARMS、腾讯 QMPM、OneAPM等都是极其优秀的监控系统。

好文推荐

  1. web极致性能优化指南
  2. 最佳实践 monorepo + pnpm + vue3 + element-plus 0-1 完整教程
  3. Vite+rollup项目如何大幅提升性能
  4. 面试官系列:请说说你对深拷贝、浅拷贝的理解
  5. 面试官系列:请你说说原型、原型链相关
  6. 面试官系类:请手写instanceof
  7. 10分钟快速手写实现:call/apply
  8. 面试官系列:请手写防抖或节流函数debounce/throttle