一般分为语法错误 和 运行时错误
// 语法错误 // 语法错误在开发阶段就会暴露,所以一般不需要对其处理 var error = 'error'; // 大写分号 // Uncaught SyntaxError: Invalid or unexpected toke // 运行时错误 error // 未定义变量 // Uncaught ReferenceError: error is not defined当不同域的脚本发生错误,无法获取错误信息
// http://localhost:5000/error.js throw Error('跨域脚本抛出的错误') // http://localhost:5000/error.js throw Error('本站脚本抛出的错误') <!-- http://localhost:3000/index.html --> <script> window.onerror = function() { console.log(arguments) return true } </script> <script src="http://localhost:3000/test.js"></script> <script src="http://localhost:5000/test.js"></script>报错:
对异常进行上报,首先程序要能感知或捕获到异常的发生并进行处理。
try catch 是代码中常用的捕获脚本错误的方式,当 try 包装的代码块报错时,catch 将捕捉到错误的信息,页面也可以继续执行,不会被阻塞。
不过 try catch 只能捕获到 同步的运行时错误,对 语法错误 和 异步错误 无能为力。
语法错误 在编辑器中开发时会直接抛出,在项目上线前就可以发现。
而 异步错误 不容易发现,需要特别注意。
try { setTimeout(() => { error }) } catch (e) { console.log('无法感知异步错误') }总结:
try catch 只能捕获 JS 脚本 同步的运行时错误无法捕获 语法错误 和 异步错误window.onerror 可以捕获 同步和异步的运行时错误。
当 JS 运行时错误发生时, window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。
可以重新自定义这个方法,对异常进行处理。
/** * @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 }; setTimeout(() => { error })不过,window.onerror 无法捕获 语法错误 和 网络请求异常错误。
语法错误 开发阶段容易察觉,所以不用特别处理。
<script> // 先定义好 onerror window.onerror = function(msg, url, row, col, err) { // ... } // 接口请求异常 无法捕获 axios({ method: 'GET', url: 'http://localhost:3000' }) </script> <!-- 静态资源请求异常 无法捕获 --> <!-- window.onerror 是 JS 脚本发生错误时触发的事件执行函数,所以静态资源请求异常无法触发该事件。 --> <img src="notfount.jpg" />总结:
在实际使用过程中,onerror 主要用来捕获预料之外的错误,而 try catch 用来在可预见情况下监控特定的错误,两者结合使用更加高效。
onerror 是 JS 脚本的全局捕获方法,最好写在所有 JS 脚本的前面,避免无法被捕获。onerror 无法捕获网络异常的错误。 静态资源 这是由于加载资源的元素触发 error 事件不会冒泡到 window,所以需要在捕获 阶段捕捉才行。 接口请求 net::ERR_CONNECTION_REFUSED onerror 无法捕获 Promise 错误 例如 axios 请求接口异常后,返回的Promise会抛出错误Promise 错误可以被 Promise catch 捕获当加载一个资源失败时,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror 处理函数。
这些 error 事件不会向上冒泡到 window,因此必须在捕获阶段将其捕捉,可以通过 window.addEventListener 设置事件传播模式 useCapture,在捕获阶段捕捉错误。
<script> window.addEventListener('error', (error) => { console.log('捕获到异常:', error); }, true) </script> <img src="notfount.jpg" />总结:
可以通过 window.addEventListener 在事件捕获阶段捕捉 静态资源请求异常不过这种方式无法判断 HTTP 的状态,所以还需要配合服务端日志进行排查分析才行。不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。需要注意避免 addEventListener 重复监听。Promise 可以使用 catch 方便的捕获它抛出的异常。
但是如果没有写 catch,以上方法都无法捕获到错误,类似 try catch,Promise catch 也相当于在可遇见情况下监视错误。
new Promise(() => { throw new Error('throw 抛出错误相当于调用 reject') }).catch(console.log) Promise.reject('reject 抛出错误').catch(console.log)axios 请求会返回一个 Promise,发送的网络请求发生异常后,Promise 会抛出错误,这时可以用 catch 捕获。
项目中可能会用到很多 Promise 实例或基于 Promise 的库(axios),为了避免漏掉 Promise 异常,可以全局注册 unhandledrejection 事件监听 未被 catch 的Promise 异常。
MDN:
当Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;这可能发生在 window下,但也可能发生在 Worker中。 这对于调试回退错误处理非常有用。
// addEventListener window.addEventListener("unhandledrejection", event => { console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`); }); // onunhandledrejection window.onunhandledrejection = event => { console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`); }; window.addEventListener('unhandledrejection', function (event) { // ...您的代码可以处理未处理的拒绝... // 防止默认处理(例如将错误输出到控制台) event.preventDefault(); });线上的项目一般会做 CDN 优化,这就会导致访问的页面和脚本来自不同的域名。
如果没有进行相应的配置,当访问的脚本报错,就会产生 Script error.。
Script error. 是浏览器在同源策略限制下产生的。
浏览器出于安全的考虑,当页面引用非同域的外部脚本文件中抛出异常的话。
当前页面是没有权力知道这个报错信息的,目的是避免数据泄露到不安全的域中。
取而代之的就是输出 Script error. 这样的信息。
解决办法:
将脚本放到同域下,这样舍弃了 CDN 的优势开启 CORS(跨源资源共享机制),从根本上解决,需要后端配置首先为页面的 script 标签添加 crossOrigin 属性。
<!-- http://localhost:3000/index.html --> <script> window.onerror = function() { console.log(arguments) return true } </script> <script src="http://localhost:5000/test.js" crossorigin></script> // http://localhost:5000/error.js throw Error('跨域脚本抛出的错误')其次需要后端为 http://localhost:5000 配置 Access-Control-Allow-Origin响应头。
以 serve 开启的web服务为例:
# --cors 命令启用 CORS 并配置 `Access-Control-Allow-Origin` 为 `*` serve . --cors监控拿到报错信息后,就需要将捕捉的错误信息发送到信息收集平台上,常用的发送形式有两种:
通过 Ajax 发送数据Ajax 请求本身也有可能会发生异常,可能引发跨域问题,所以更推荐使用动态创建 img 标签的形式进行上报。
动态创建按 img 标签 function report (error) { let reportUrl = 'http://xxx/report' new Image().src = reportUrl + '?error=' + error }现在大多数项目都是通过 webpack 等工具打包压缩后发布到线上的。
多个文件压缩打包成一个文件,变量名被替换成了简短的未知字符串,代码被合并成了一行。
想要定位错误在源代码中的位置,就要使用 Source Map。
参考 脚本错误量极致优化-让脚本错误一目了然
如果网站访问量很大,错误采集就会有两个问题:
上报请求次数庞大错误日志记录太多如果没有必要将所有错误信息全部采集下来,可以设置一个采集率,减少采集的信息量。
采集率可以通过使用任意方式设定,随机数、时间、用户特征等。
function report (error) { // 使用一个随机数 if (Math.random() < 0.3) { let reportUrl = 'http://xxx/report' new Image().src = reportUrl + '?error=' + error } }如果不需要及时记录错误,可以先将错误日志存储到客户端,定时执行上报操作。
例如:
发生异常,捕获错误信息,存储到 localStorage对比当前时间和上次上报的时间如果大于10分钟,就执行上报,并清空本地存储的日志如果小于10分钟,就不作处理当然,这种方式可能也会丢失一些记录,例如某个客户端在上报前退出,并再也不访问网站。
GitHub 搜索关键字 前端异常 或 monitor,筛选 JavaScript 可以查到一些开源的监控方案,例如:
webfunny_monitormonitorFEerror-moniter