前言
在前端开发过程中,我们经常会碰到一些错误,那么遇到错误该如何处理呢?这篇文章主要介绍常见的错误类型和处理异常的方案。
常见的错误类型
首先,我们需要了解常见的错误一般有哪些,表示什么意思。主要分为以下几种情况。
- RangeError: 标记一个错误,当设置的数值超出相应的范围触发。比如new Array(-2000)
- ReferenceError: 引用类型错误,当一个不存在的变量被引用时发生的错误。比如console.log(a)
- SyntaxError: 语法错误。比如if(true) {
- TypeError: 类型错误,表示值的类型非预期类型时发生的错误。
- ResourceError: 资源加载错误
- HttpError: http请求错误
搜集错误
对于出现的错误,我们需要捕获它,只有捕获到了才能让其执行对应的操作。 以下是错误的捕获方式
1. try/catch
能捕获常规运行时错误,但是对于语法错误和异步错误不行
// 常规运行时错误,可以捕获
try {
console.log(notdefined);
} catch(e) {
console.log('捕获到异常:', e);
}
// 语法错误,不能捕获
try {
const notdefined,
} catch(e) {
console.log('捕获到异常:', e);
}
// 异步错误,不能捕获
try {
setTimeout(() => {
console.log(notdefined);
}, 0)
} catch(e) {
console.log('捕获到异常:', e);
}
2. window.onerror
当JS运行时错误发生时,window会触发一个ErrorEvent接口的error事件。
/**
* @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});
};
console.log(notdefined);
// 语法错误,不能捕获
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:', {message, source, lineno, colno, error});
};
const notdefined,
// 异步错误,可以捕获
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:', {message, source, lineno, colno, error});
};
setTimeout(() => {
console.log(notdefined);
}, 0);
// 资源错误,不能捕获
<script>
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕获到异常:', {message, source, lineno, colno, error});
return true;
};
</script>
<img src="https://yun.tuia.cn/image/kkk.png">
3. window.addEventListener
当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个Event接口的error事件,这些error事件不会向上冒泡到window,但能被捕获。而window.onError不能检测捕获。
// 图片、script、css加载错误,都能被捕获
<script>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<img src="https://yun.tuia.cn/image/kkk.png">
<script src="https://yun.tuia.cn/foundnull.js"></script>
<link href="https://yun.tuia.cn/foundnull.css" rel="stylesheet"/>
// new Image错误,不能捕获
<script>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<script>
new Image().src = 'https://yun.tuia.cn/image/lll.png'
</script>
// fetch错误,不能捕获
<script>
window.addEventListener('error', (error) => {
console.log('捕获到异常:', error);
}, true)
</script>
<script>
fetch('https://tuia.cn/test')
</script>
小结:其中new Image比较重要(特别),可以单独自己处理自己的错误。但是对于通用fetch,返回来的是Promise.
4. Promise错误的解决
Promise错误分为普通Promise错误和async错误
普通Promise错误
// try/catch 不能处理JSON.parse的错误,因为它在Promise中
try {
new Promise((resolve, reject) => {
JSON.parse('');
resolve();
})
} catch(err) {
console.error('in try catch', err);
}
// 需要使用catch方法
new Promise ((resolve, reject) => {
JSON.parse('');
resolve();
}).catch(err => {
console.error('in try catch', err);
})
async错误
上面说的普通的promise错误可以通过async/await来解决。但是有一点比较特殊的是:try/catch不能捕获async包裹的错误。
const getJSON = async () => {
throw new Error('inner error');
}
// 通过try/catch处理
const makeRequest = async () => {
try {
// 捕获不到
JSON.parse(getJSON());
} catch (err) {
console.log('outer', err);
}
};
try {
// try/catch不到
makeRequest();
} catch (err) {
console.error('in try catch', err);
}
try {
// 需要await,才能捕获到
await makeRequest();
} catch (err) {
console.error('in try catch', err);
}
import chunk错误
import其实返回的也是一个promise。它是一个特殊的语法-在w3c的不懈努力下,import支持不必写在最顶部的写法,因此我们有如下两种方式捕获错误:
// Promise catch方法
import (/* webpackChunkName: "incentive" */'./index').then(module => {
module.default();
}).catch((err) => {
console.error('in catch fn', err);
})
// await 方法,try catch
try {
const module = await import(/* webpackChunkName: "incentive" */'./index');
module.default();
} catch(err) {
console.error('in try catch', err)
}
小结:以上三种归结为Promise的错误,可以通过unhandledrejection API捕获:
// 全局处理统一Promise
window.addEventListener("unhandledrejection", function(e){
console.log('捕获到异常:',e);
});
为了防止有漏掉的Promise异常,可通过unhandledrejection用来全局监听Uncaught Promise Error。
Vue中的错误
由于vue会捕获所有Vue单文件组件或者Vue.extend继承的代码,所以在Vue里面出现的错误,并不会直接被window.onerror捕获,而是会抛给Vue.config.errorHandler。
/**
* 全局捕获Vue错误,直接扔出给onerror处理
*/
Vue.config.errorHanlder = function (err) {
setTimeout(() => {
throw err
})
}
跨域问题
一般情况,如果出现Script error这样的错误,基本上可以确定是出现了跨域问题。
如果当前投放页面和云端JS所在不同域名,如果云端JS出现错误,window.onerror会出现Script Error。通过以下两种方法能够给予解决:
后端配置Access-Control-Allow-Origin、前端script加crossorigin。
<script src="http://yun.tuia.cn/test.js" crossorigin></script>
const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = "http://yun.tuia.cn/test.js";
document.body.appendChild(script);
如果不能修改服务端的请求头,可以考虑通过使用try/catch绕过,将错误抛出:
<!doctype html>
<html>
<head>
<title>Test page in http://test.com</title>
</head>
<body>
<script src="https://yun.dui88.com/tuia/cdn/remote/testerror.js"></script>
<script>
window.onerror = function (message, url, line, column, error) {
console.log(message, url, line, column, error);
}
try {
foo(); // 调用testerror.js中定义的foo方法
} catch (e) {
throw e;
}
</script>
</body>
</html>
我们捋一下场景,一般调用远端js,有下列三种常见情况。
调用远端JS的方法出错
远端JS内部的事件出问题
要么在setTimeout等回调内出错
上报接口
捕获到了错误,就要开始往服务端发送(可以生成日志或者异常文档,一般监控工具会自动完成)。这其实就是一次请求的过程。
里面有几个需要注意的地方:
为什么不能直接用ajax - GET/POST/HEAD请求接口进行上报?
一般而言,打点域名都不是当前域名,所以所有的接口请求都会构成跨域。
为什么不能用请求其他的文件资源(js/css/ttf)的方式进行上报?
一般来说创建资源节点后只有将对象注入到浏览器DOM树后,浏览器才会实际发送资源请求。而且载入js/css资源还会阻塞页面渲染,影响用户体验。
构造图片打点不仅不用插入DOM,只要在js中new出Image对象就能发起请求,而且还没有阻塞问题,在没有js的浏览器环境中也能通过img标签正常打点。
使用new Image进行接口上报。最后一个问题,同样都是图片,上报时选用了1x1的透明GIF,而不是其他的PNG/JEPG/BMP文件。
为什么推荐采用1x1的gif图片进行操作?
首先,1x1像素是最小的合法图片。而且,因为是通过图片打点,所以图片最好是透明的,这样一来不会影响页面本身展示效果,二者表示图片透明只要使用一个二进制位标记图片是透明色即可,不用存储色彩空间数据,可以节约体积。因为需要透明色,所以可以直接排除JEPG。
同样的响应,GIF可以比BMP节约41%的流量,比PNG节约35%的流量。GIF才是最佳选择。