背景
稳定性建设之JavaScript代码不能被阻断
js代码可能会因为某些原因,导致出错,进而整个后续代码有可能都被阻断。直接影响线上的稳定性
最常见的js被阻断的情况
1
2
3
4
5
6
|
console.log(111)
// 预期 a = {}
// 结果
a = undefined
a.a = 1
console.log(222) // js代码不能执行到这一行
|
这个代码很明显会报错,在a.a = 1这一行开始报错,后续的js代码被阻断了,console.log(222)打印不出来
解决办法
- 解决办法也很简单,用 try...catch... 捕获住错误就好了
1
2
3
4
5
6
7
8
9
10
|
console.log(111)
try {
// 预期 a = {}
// 结果
a = undefined
a.a = 1
} catch (e) {
console.error(e)
}
console.log(222) // js代码可以执行到这一行
|
容易被我们忽视的点
1. 没考虑到错误上报
- 上面的demo没有考虑错误上报,发生错误时,外部根本捕获不到(即使你接入了sentry类的产品),因为error被try catch给吃掉了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
try {
// 预期 a = {}
// 结果
a = undefined
a.a = 1
} catch (e) {
console.error(e)
// 公司内部的上报函数
someReportFunction('sendEvent', {
name: 'try_catch_error',
params: {
errorMsg: e.message,
errorStack: e.stack
},
});
}
|
2. 错用throw
随便点开一篇文章,就有人在误人子弟,教别人用 throw, throw这个东西是不能乱用的,因为他会阻断代码,重要的事情说三遍,throw会阻断代码,throw会阻断代码,throw会阻断代码
例如:
1
2
3
4
5
6
7
8
9
10
11
|
console.log(111)
try {
// 预期 a = {}
// 结果
a = undefined
a.a = 1
} catch (e) {
console.error(e)
throw e // throw会阻断代码,导致下面不执行
}
console.log(222) // 不能执行到这一行
|
当然throw也不是一无是处,但是,他只能在try{ 里面使用 },不能在try之外的地方使用throw,包括catch
1
2
3
4
5
6
7
|
console.log(111)
try {
throw new Error(111)
} catch (e) {
console.error(e)
}
console.log(222)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function getData () {
if (...) {
...
} else {
throw new Error(111)
}
}
console.log(111)
try {
getData()
} catch (e) {
console.error(e)
}
console.log(222)
|
3. 异步代码catch不到,还是会被阻断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
console.log(111111111)
try {
setTimeout(() => {
a = undefined
a.a = 1 // 代码被阻断于此
console.log('error') // 不能执行到这一行
}, 0)
} catch (e) {
console.error(e) // 异步代码catch不到
}
console.log(222222222)
setTimeout(() => {
console.log('setTimeout') // 浏览器可以执行到这一行,node的不行(node14和16版本都test了)
}, 2000)
|
4. import()和require()的错误捕获表现不一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// a.js
console.log(111111111)
try {
require('./b.js')
} catch (e) {
console.log('error') // 错误会被正常catch到
console.error(e)
}
console.log(222222222)
setTimeout(() => {
console.log('setTimeout')
}, 2000)
// b.js
console.log(1)
a = undefined
a.a = 1
console.log(2)
// 结果打印 (require被正常捕获)
111111111
1
error
TypeError: Cannot set property 'a' of undefined
...
...
222222222
setTimeout
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// a.js
console.log(111111111)
try {
import('./b.js')
} catch (e) {
console.log('error') // 错误没有被catch到
console.error(e)
}
console.log(222222222)
setTimeout(() => {
console.log('setTimeout')
}, 2000)
// b.js
console.log(1)
a = undefined
a.a = 1
console.log(2)
// 结果打印 (import的 错误没有被catch到)
111111111
222222222
1
(node:92673) UnhandledPromiseRejectionWarning: TypeError: Cannot set property 'a' of undefined
...
setTimeout
|
正确捕获import()的方式:其实import()是一个promise,用promise的方法去catch就好了
1
2
3
4
5
|
import('./b.js')
.catch(e => {
console.log('error')
console.error(e)
})
|
结论:
- try catch 不能捕获import()模块的错误,require可以被捕获
- import() 用promise的方法去catch就好了
背景:
- require是运行时加载(可以理解为,函数调用)
- import()是动态import,会延迟加载,是异步任务(微任务),是promise
|