不知道小伙伴们是否有这样的体验,本地开发项目调试时一切正常,一旦发布到线上就会出现各类奇怪的问题,缘由也是多种多样,环境变量不一样,宿主环境不一样,接口返回的数据格式不一样,代码逻辑问题等等。css
有些错误在开发和测试环节能被发现,有些错误要到了线上才会被发现,此时傻乎乎的等着用户反馈未免太过被动,因此,一个收集可以捕获代码错误的监控系统就显得尤其重要。html
该系列文章目的并非让你们可以立马打造一个前端代码监控系统,是旨在介绍浏览器环境下捕获代码错误的经常使用方式,并经过咱们所熟知的方式,打造一个前端收集代码错误的插件,最后再简单介绍一下收集到了报错信息,如何快速定位错误。前端
在开始咱们的课题以前,咱们首先须要了解一下常见的错误类型。vue
此类错误是最容易排除的,咱们每每只须要借助编辑器的自动检测,或者 eslint
tslint
等三方工具,在开发阶段就能解决。node
固然,若是有同窗是用文本编辑器书写代码的,请在收下我膝盖的同时,下载一个 vscode 体验更好的代码编写环境。react
function foo() cont bar = "string"
这个能够说是平时开发中遇到最多的了,好比下面这个例子,相信有很多同窗在使用 vue
或者 react
时,经过接口获取列表数据,若是后端同窗对于空数据没有返回 []
,而是返回了 null
或者 undefined
,前端展现就会出现问题。jquery
也有的同窗会说,ts 大法好,接口定义香香的(但其实这并不能解决接口返回不一致的问题),并且,若遇到同事类型定义皆 any
的状况。。。git
undefined.map((item) => console.log(item));
其余的错误类型,好比使用了一个未定义的变量,但其实这种错误均可以在开发阶段规避掉。github
console.log(helloWorld);
不知道小伙伴们在使用 promise
的时候有没有 catch
reject
的习惯,我相信除了我大部分人都是有的(违心),那对于未主动 catch
的 reject
咱们应该如何捕获并处理?ajax
const promise = () => new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("promise reject")); }, 1000); }); // 未使用 catch 捕获错误 promise().then(console.log);
资源加载错误也是一个使人头秃的问题,若是只是 image
那还好,如果重要的 js
、css
资源加载错误。。。
<!-- 你说这个资源能加载?嗯?你有问题,小老弟 --> <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
接下来就咱们说的几种报错类型来作捕获,有的同窗可能会说,哎呀我平时用的是 vue
react
,这个这么简单的场景我不可能会犯错的啦。
嘘,憋说话,框架们抛出错误的方式也是用那些简单的方法。
另外,下面说内容都有详细的代码和注释,小伙伴们能够直接 clone
代码在本地进行调试,具体说明能够看项目分支的 README.md
。
git clone -b demo/catch-code-error https://github.com/jsjzh/tiny-codes.git
在平时写代码的时候,若是遇到可能会执行错误的逻辑,咱们通常会用 try catch
,包裹,而后单独作处理,可是对于一些意料以外的错误,好比上面的接口返回;还有异步执行的代码中若发生错误,经过 try catch
是没法捕获到错误的,这个时候,window.onerror
就派上用场了。
咱们能够这么理解,try catch
用来捕获能够预料到的错误,window.onerror
用来捕获预料以外的错,也就是兜底策略。
/** * message {String}: 错误信息 * fileName {String}: 发生错误的文件 * col {Number}: 错误代码的行 * row {Number}: 错误代码的列 * error {Error}: Error 对象 */ window.onerror = (message, fileName, col, row, error) => { console.log("message: ", message); console.log("fileName: ", fileName); console.log("col: ", col); console.log("row: ", row); console.log("error.name: ", error.name); console.log("error.message: ", error.message); console.log("error: ", error); };
下面的代码咱们主动 try catch
了错误,说明咱们知道 try
的逻辑里面可能会出错,在 catch
里面作了处理,其实这种错误状况有多是不须要上报的,由于咱们已经作了对应的错误处理。
ps: 若认为仍旧须要上报错误,能够在catch
中增长一段throw error
的逻辑,这样就能够将错误抛出,被window.onerror
捕获。
// 同步执行代码,经过 try catch 能够捕获错误 try { console.log(helloWorld); } catch (error) { // 在这里作一些自定义处理 // ... // 若作了自定义处理后,仍旧须要上报错误,增长 throw error // throw error; }
如下这种状况,咱们没有预料到会报错,则报错会直接被 window.onerror
捕获。
ps: 这里在浏览器环境访问了process
对象,这是node
环境才有的全局变量。
// 未使用 try catch 捕获错误 // 说明咱们未预料到这段代码可能出错 console.log(process);
异步的逻辑代码中如有代码执行错误,咱们经过 try catch
没法直接捕获到,这个时候有两种办法,第一种是给异步执行的函数包裹 try catch
,这种咱们就不举例了,第二种方法是经过 window.onerror
来捕获错误。
const promise = () => new Promise(() => { setTimeout(() => { console.log(helloWorld); }, 1000); }); try { promise().then(); } catch (error) { // 没法捕获到错误 error console.log("can't catch error"); }
能够看到上图,错误被 window.onerror
给捕获了。
在说异步代码 reject
前,咱们得有一个共识,promise
的 catch
没法捕获到代码执行错误,只有经过 reject()
的方式抛出的异常,才会被 promise.catch
捕获到,什么意思?看下面代码。
const promise = () => new Promise(() => { setTimeout(() => { console.log(helloWorld); }, 1000); }); promise() .then() // 没法捕获到 helloWorld 的错误 .catch(() => { console.log("can't catch error"); });
那 promise
的 catch
能够捕获到什么错误呢?答案是经过 reject
抛出的错误,以下。
const promise2 = () => new Promise((resolve, reject) => { setTimeout(() => { // 经过 reject 抛出错误 reject(new Error("promise reject")); }, 1000); }); promise2() .then() .catch((error) => { // 可以捕获到错误 console.log("error.name: ", error.name); console.log("error.message: ", error.message); console.log("error: ", error); });
上面的内容只是为了达成咱们之间的共识,接下来咱们来讲说如何捕获未 catch
的 reject
,经过 window.onunhandledrejection
。
ps: 有一点须要提醒的,对于抛出的自定义错误,尽可能使用
new Error(...)
,这样咱们不只能够获取到完整的错误信息,还能够获取到文件名,出错行以及出错列,也就是所谓的堆栈信息。ps2: 若是是被
promise.catch
捕获的reject
,则不会被window.onunhandledrejection
捕获,若仍想将此错误上报,能够经过throw error
的方式,此时将被window.onerror
捕获。
window.onunhandledrejection = (promiseRejectEvent) => { // Error 对象,由 reject(new Error()) 生成 const reason = promiseRejectEvent.reason; if (reason) { console.log("reason.name: ", reason.name); console.log("reason.message: ", reason.message); console.log("reason.stack: ", reason.stack); } }; const promise2 = () => new Promise((resolve, reject) => { setTimeout(() => { // 经过 reject 抛出错误 reject(new Error("promise reject")); }, 1000); }); // 未使用 catch,错误将被 window.onunhandledrejection 捕获 promise2().then();
对于资源加载错误,不论是 js
、css
、image
,咱们均可以经过 window.addEventListener("error")
来监听捕获错误。
可是对于使用 background-image: url(./error.png)
致使的资源加载错误,以及 new Image().src = "./error.png"
致使的资源加载错误,经过该方法没法捕获到。
ps: 如有小伙伴知道有什么办法能够将这些加载错误捕获到的,还劳烦告诉我,我自测成功后会更新到文章中。
window.addEventListener( "error", (sourceErrorEvent) => { const targetElement = sourceErrorEvent.target || sourceErrorEvent.srcElement; const url = targetElement.src || targetElement.href; console.log("sourceError: ", url); }, true );
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script> <script src="./error.js"></script> <link rel="stylesheet" href="./error.css" /> <img src="./error.png" />
相信有了上面的三个方案,咱们已经可以捕获大部分报错了,可是,在调试的时候发现,当加载的 js
资源是跨域的,window.onerror
就只会收集到错误信息 Script error.
,以下图。
为啥会这样?固然就是浏览器的策略。。。不过咱们也有对应的解决办法。
ps: 关于某些
html
标签资源跨域内容能够参见 MDN 的
crossOrigin 属性说明。
回到问题,咱们只须要作两件事,第一:
给跨域加载的 js
资源增长一个 crossOrigin="anonymous"
属性
<script src="http://127.0.0.1:7002/index.js" crossorigin="anonymous"></script>
第二:
保证跨域资源的服务器设置了 Access-Control-Allow-Origin: hosts
头,而且你的浏览器发送 Origin: host
在其列表内,嫌麻烦的话,服务端能够直接设置 Access-Control-Allow-Origin: *
。
ps: 其实不太建议设置成
*
,若是你使用了cookie
,这会致使cookie
没法正常工做,还须要配套设置一些其余的头部,才能使cookie
生效。
固然,若是资源都是统一作了 CDN 配置的,那能够为静态资源的服务器单独配置头部。ps2: 你不须要主动添加
Origin
头部,这是浏览器的自发行为
# Response Header ... Access-Control-Allow-Origin: http://127.0.0.1:7001 ... # Request Header ... Origin: http://127.0.0.1:7001 ...
如上,咱们就能够获取到错误的详细信息了。
再小小的强调一下,以上的例子你均可以在以下示例中找到。
git clone -b demo/catch-code-error https://github.com/jsjzh/tiny-codes.git
至此,捕获代码错误的正确姿式(一)就结束了,在书写文章的时候了解了许许多多平时不会关注的点,对于神奇的前端代码监控系统也有了本身的一些心得体会。
固然,有的同窗要说了,sentry 不香嘛?为啥要费时费力本身瞎折腾?关于这点我认为,若是咱们只是为了解决问题,那现成的方案拿来用天然是最好的。但不折腾的话和咸鱼有啥区别?(逃
文章后续还有两个章节,第二章我会打造一个专门收集前端错误信息的插件,第三章我会对于如今很常见的代码压缩后如何定位问题提供一些本身不太成熟的方案。
另,文笔拙劣,方案幼稚,看官们如有任何意见或建议,欢迎在评论区留言,咱们一块儿讨论讨论。
代码即人生,我甘之如饴。
技术不断在变
头脑一直在线
前端路漫漫
咱们下期见by --- 裤裆三重奏
我在这里 gayhub@jsjzh 欢迎你们来找我玩儿。
欢迎小伙伴们直接加我,拉你进群一块儿搞事情,记得备注一下你是从哪里看到文章的。
ps: 若是图片失效,能够加我 wechat: kimimi_king