js中的错误处理(try...catch...finally,异步错误)

错误处理

在执行JavaScript代码的时候,有些情况下会发生错误,错误分两种:
1.一种是程序写的逻辑不对,导致代码执行异常。例如:
var s = null;
var len = s.length; // TypeError:null变量没有length属性
对于这种错误,要修复程序。
2.另外一种是执行过程中,程序可能遇到无法预测的异常情况而报错,例如,网络连接中断,读取不存在的文件,没有操作权限等。
对于这种错误,我们需要处理它,并可能需要给用户反馈。

错误处理是程序设计时必须要考虑的问题。对于C这样贴近系统底层的语言,错误是通过错误码返回的:

int fd = open("/path/to/file", O_RDONLY);复制代码
if (fd == -1) {复制代码
    printf("Error when open file!");复制代码
} else {复制代码
    // TODO复制代码
}复制代码

通过错误码返回错误,就需要约定什么是正确的返回值,什么是错误的返回值。上面的 open()函数约定返回 -1表示错误。
显然,这种用错误码表示错误在编写程序时十分不便。
因此,高级语言通常都提供了更抽象的错误处理逻辑try ... catch ... finally,JavaScript也不例外。

try ... catch ... finally

使用try ... catch ... finally处理错误时,我们编写的代码如下:

let r1,r2,s = null;复制代码
try{复制代码
    r1 = s.length;    // 此处会报错复制代码
    r2 = 100;复制代码
} catch {复制代码
    console.log('出错' + e)复制代码
} finally {复制代码
    cosole.log('finally')复制代码
}复制代码
console.log('r1 = ' + r1 )      // r1 is undefined复制代码
console.log('r2 = ' + r2 )      // r2 is undefined复制代码

运行后可以发现,输出提示类似“出错了:TypeError: Cannot read property 'length' of null”。
我们来分析一下使用try ... catch ... finally的执行流程。
当代码块被 try { ... }包裹的时候,就表示这部分代码执行过程中可能会发生错误,一旦发生错误,就不再继续执行后续代码,转而跳到 catch块。
catch (e) { ... }包裹的代码就是错误处理代码,变量 e表示捕获到的错误。

最后,无论有没有错误,finally一定会被执行

执行流程

所以,有错误发生时,执行流程像这样:
先执行 try { ... }的代码;
执行到出错的语句时,后续语句不再继续执行,转而执行 catch (e) { ... }代码;
最后执行 finally { ... }代码。
而没有错误发生时,执行流程像这样:
先执行 try { ... }的代码;
因为没有出错,catch (e) { ... }代码不会被执行;
最后执行 finally { ... }代码。
最后请注意,catch和 finally可以不必都出现。
也就是说,try语句一共有三种形式:

完整的try ... catch ... finally:复制代码
try {复制代码
    ...复制代码
} catch (e) {复制代码
    ...复制代码
} finally {复制代码
    ...复制代码
}复制代码
只有try ... catch,没有finally:复制代码
try {复制代码
    ...复制代码
} catch (e) {复制代码
    ...复制代码
}复制代码
只有try ... finally,没有catch:复制代码
try {复制代码
    ...复制代码
} finally {复制代码
    ...复制代码
}复制代码

错误类型

JavaScript有一个标准的 Error对象表示错误,还有从 Error派生的 TypeError、ReferenceError等错误对象。我们在处理错误时,可以通过 catch(e)捕获的变量 e访问错误对象:

try {复制代码
    ...复制代码
} catch (e) {复制代码
    if (e instanceof TypeError) {复制代码
        alert('Type error!');复制代码
    } else if (e instanceof Error) {复制代码
        alert(e.message);复制代码
    } else {复制代码
        alert('Error: ' + e);复制代码
    }复制代码
}复制代码

使用变量 e是一个习惯用法,也可以以其他变量名命名,如 catch(ex)。

抛出错误

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

throw new Error('这是一个错误')复制代码

例如,前端让用户输入一个数字,程序接收到的实际上是一个字符串,然后用 parseInt()转换为整数。当用户输入不合法的时候,我们就抛出错误;

实际上,JavaScript允许抛出任意对象,包括数字、字符串。但是,最好还是抛出一个Error对象。
最后,当我们用catch捕获错误时,一定要编写错误处理语句:

var n = 0, s;复制代码
try {复制代码
    n = s.length;复制代码
} catch (e) {复制代码
    console.log(e);复制代码
}复制代码
console.log(n);复制代码
哪怕仅仅把错误打印出来,也不要什么也不干:复制代码
var n = 0, s;复制代码
try {复制代码
    n = s.length;复制代码
} catch (e) {复制代码
}复制代码
console.log(n);复制代码

因为catch到错误却什么都不执行,就不知道程序执行过程中到底有没有发生错误。
处理错误时,请不要简单粗暴地用 alert()把错误显示给用户。教程的代码使用 alert()是为了便于演示

错误传播

来源于:公众号———前端大牛爱好者

如果代码发生了错误,又没有被try ... catch捕获,那么,程序执行流程会跳转到哪呢?

function getLength(s) {复制代码
    return s.length;复制代码
}复制代码
复制代码
function printLength() {复制代码
    console.log(getLength('abc')); // 3复制代码
    console.log(getLength(null)); // Error!复制代码
}复制代码
复制代码
printLength();复制代码

错误也具有冒泡传播性
如果在一个函数内部发生了错误,它自身没有捕获,错误就会被抛到外层调用函数,如果外层函数也没有捕获,该错误会一直沿着函数调用链向上抛出,直到被JavaScript引擎捕获,代码终止执行。所以,我们不必在每一个函数内部捕获错误,只需要在合适的地方来个统一捕获,一网打尽:

当bar()函数传入参数null时,代码会报错,错误会向上抛给调用方foo()函数,foo()函数没有try ... catch语句,所以错误继续向上抛给调用方 main()函数,main()函数有try ... catch语句,所以错误最终在main()函数被处理了。
至于在哪些地方捕获错误比较合适,需要视情况而定。

异步错误处理

来源于 廖雪峰老师博客

编写JavaScript代码时,我们要时刻牢记,JavaScript引擎是一个事件驱动的执行引擎,代码总是以单线程执行,而回调函数的执行需要等到下一个满足条件的事件出现后,才会被执行。
例如,setTimeout()函数可以传入回调函数,并在指定若干毫秒后执行:

function printTime() {复制代码
    console.log('It is time!');复制代码
}复制代码
复制代码
setTimeout(printTime, 1000);复制代码
console.log('done');复制代码

上面的代码会先打印done,1秒后才会打印It is time!。
如果printTime()函数内部发生了错误,我们试图用try包裹setTimeout()是无效的:

异步错误只能在异步的函数内部捕获和处理

原因就在于调用 setTimeout()函数时,传入的 printTime函数并未立刻执行!紧接着,JavaScript引擎会继续执行 console.log('done');语句,而此时并没有错误发生。直到1秒钟后,执行 printTime函数时才发生错误,但此时除了在 printTime函数内部捕获错误外,外层代码并无法捕获。
所以,涉及到异步代码,无法在调用时捕获,原因就是在捕获的当时,回调函数并未执行。
类似的,当我们处理一个事件时,在绑定事件的代码处,无法捕获事件处理函数的错误。
例如,针对以下的表单:

<form>复制代码
    <input id="x"> + <input id="y">复制代码
    <button id="calc" type="button">计算</button>复制代码
</form>复制代码
复制代码


我们用下面的代码给button绑定click事件:

try{复制代码
    $btn.click(function(){复制代码
        var x = parseFloat($('#x').val()),复制代码
        var y = parseFloat($('#y').val()),复制代码
            r;复制代码
        if(isNaN(x) || isNaN(y)){复制代码
            throw new Error('输入有误');复制代码
        }复制代码
        r = x + y;复制代码
        alert('计算结果:' + r);复制代码
    })复制代码
} catch(e) {复制代码
    alert('输入有误')复制代码
}复制代码

但是,用户输入错误时,处理函数并未捕获到错误。请修复错误处理代码。

参考文章:
(1) mp.weixin.qq.com/s?__biz=MzU…
(2) mp.weixin.qq.com/s?__biz=MzU…
(3)mp.weixin.qq.com/s/oJJNxgG95…


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值