JavaScript-错误处理

Error

错误分两种:

  1. 程序逻辑写得不对,代码执行异常

  2. 执行过程中,程序可能遇到无法预测得异常情况而报错,例如网络连接中断,读取不存在的文件,没有操作权限等

    对于这种错误,我们需要处理它,并可能需要反馈给用户

对于C这种贴合系统底层的语言,错误是通过错误码返回的.

但是错误码在写代码时很不方便,所以高级语言使用更抽象的错误处理逻辑.

try…catch…finally

1
2
3
4
5
6
7
8
9
10
11
let r1, r2, s = null;
try {
r1 = s.length; // 这里会产生错误
r2 = 100; // 该语句不会执行
} catch (e) {
console.log('出错了: ' + e);
} finally {
console.log('finally');
}
console.log('r1 = ' + r1); // undefined
console.log('r2 = ' + r2); // undefined

输出如下:

1
2
3
4
出错了:TypeError: Cannot read properties of null (reading 'length')
finally
r1 = undefined
r2 = undefined

分析下try...catch...finally的执行顺序:

  1. 先执行try{...}的代码
  2. 如果出错
    1. 后续语句不再执行,直接跳转到catch{...}执行里面的代码
  3. 如果没有出错
    1. catch{...}则不会被执行
  4. 最后执行finally{...}代码

另外catch{...}finally{...}可以都不出现

错误类型

  • Error: JS标准的错误对象,从它派生出来的有
    • TypeError
    • ReferenceError

这些错误对象都可以通过catch(e)捕获到e变量中.

1
2
3
4
5
6
7
8
9
10
11
try {
...
} catch (e) {
if (e instanceof TypeError) {
alert('Type error!');
} else if (e instanceof Error) {
alert(e.message);
} else {
alert('Error: ' + e);
}
}

当然,e的命名没有固定,你可以随便.

抛出错误

程序可以主动抛出错误,让执行流程直接跳转到catch块,使用throw语句抛出.

1
2
3
4
5
6
7
8
9
10
11
12
let r, n, s;
try {
s = prompt('请输入一个数字');
n = parseInt(s);
if (isNaN(n)) {
throw new Error('输入错误');
}
r = n*n;
console.log(n + ' * ' + n + ' = ' + r);
} catch (e) {
console.log('出错了: ' + e);
}

实际上,JS允许抛出任意对象,比如数字,字符串.不过最好还是抛出一个Error.

最后,catch中一定要写错误处理语句,哪怕只是把错误打印出来,也不要什么都不干.

另外处理错误时,不要简单粗暴地用alert显示给用户.

错误传播

当一个函数内部发生了错误,它自身没有捕获,错误就被抛到外层调用函数,如果外层调用函数也没有捕获,该错误会一直沿着函数调用链向上抛出,直到被JS引擎捕获,代码终止.

所以我们不必在每一个函数内部捕获错误,只需要在合适的地方统一捕捉.

至于在哪些地方捕捉错误更适合,需要视情况而定.

异步错误处理

1
2
3
4
5
6
function printTime() {
console.log('It is time!');
}

setTimeout(printTime, 1000);
console.log('done');

上面的代码会先打印done,一秒后才会打印It is time!.

此时如果使用try包裹setTimeout,而错误发生在printTime函数内部,try...catch无法捕获到对应的错误的.

原因在于:

  • 调用setTimeout()时,传入的printTime函数不会马上执行.
  • JS引擎会先执行console.log('done');,此时并没有错误产生
  • 一秒钟后再调用printTime(),但printTime()内部和外部都没有捕获错误的代码.

类似的,当我们处理一个事件时,在绑定事件的代码处,无法捕获处理函数的错误.

1
2
3
4
5
<!-- 有以下表单 -->
<form>
<input id="x"> + <input id="y">
<button id="calc" type="button">计算</button>
</form>

image-20241120145428902

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
27
let bin = document.querySelector('#calc');
/*
querySelector(): 用于选择文档中匹配指定CSS选择器的第一个元素,如果没有就返回null
'#calc': 这是一个 CSS 选择器,用于选择 ID 为 "calc" 的元素。# 符号表示 ID 选择器。
*/


// 取消已绑定的事件
btn.onclick = null;
/*
这个代码的作用是清除之前可能已经绑定到该按钮上的任何点击事件处理程序,这是一种确保只绑定一个特定事件处理程序的常见做法,避免意外的重估触发或行为冲突.
*/

btn.onclick = function() {
try { // 注意try的位置,它应该在事件处理函数内部,才能捕获
let x = parseFloat(document.querySelector('#x').value);
let y = parseFloat(document.querySelector('#y').value);
let r;
if (isNaN(x) || isNaN(y)) {
throw new Error('输入有误!');
}
r = x + y;
alert('计算结果: ' + r);
} catch(e) {
alert('输入有误!' + (e.message ? ': ' + e.message : '')
}
}

JavaScript-错误处理
http://example.com/2024/11/20/js-error/
作者
Peter Pan
发布于
2024年11月20日
许可协议