重温JavaScript(lesson22)Error对象和异常处理

这次我们要一起重温JS有关异常和错误处理的内容。“人们都希望生活在一个不会出错的世界里,但这只是一种奢望。即便是最小的应用,都难免因为一些无法事先预料的情况而产生错误。所以,编写能够稳定运行的高质量软件的第一步,就是要承认软件会有错误。第二步就是提前识别出那些错误,并以恰当的方式处理它们”—《JavaScript学习指南》。

注意:《JavaScript学习指南》是一本学习JavaScript的好书,简洁而又全面,本文的示例代码参考了这本书。

异常处理是一种以可控的方式处理错误的机制。之所以叫做异常处理,是因为其本意是处理异常情况。

当代码中有错误发生时,一个好的处理机制可以帮助我们理解错误发生的原因,并且使我们能以一种较为优雅的方式来纠正错误。—《JavaScript面向对象编程指南(第二版)》

1.Error对象的介绍

在JavaScript中,将会使用try、catch及finally语句组合来处理错误。当程序中出现错误时,就会抛出一个Error对象。

Error对象是JS的内建对象,可以用它来处理任意类型的错误。Error对象可能由以下几个内建的构造器中的一个产生而成:

EvalError、RangeError、ReferenceError、SyntaxError、TypeError和URIError,所有的这些对象都继承自Error对象。

如果在浏览器控制台中调用一个不存在函数,那么就会报错,这个错误表示是引用错误ReferenceError。

图片

错误的显示方式根据浏览器和宿主环境的不同会有所差异。

下面我们来看一下Error对象的定义和使用:

function validateEmail(email) {
    return email.match(/@/) ? email : new Error('此邮箱非法')
}
const testEmail = 'newname@js.com'
const res = validateEmail(testEmail)
if(res instanceof Error) {
    console.error(`Error: ${res.message}`)
} else {
    console.log(res)
}
const testEmail2 = 'newName.js.study'
const res2 = validateEmail(testEmail2)
if(res2 instanceof Error) {
    console.error(`Error: ${res2.message}`)
} else {
    console.log(res2)
}

代码运行结果如下图所示:

图片

说明一下:我们定义了一个邮箱验证函数对邮箱验证,如果邮箱含有@符号则认为是一个合法的邮箱,并返回合法邮箱;否则认为是一个不合法的邮箱,并返回一个错误。使用了两个邮箱对函数进行了测试,分别返回邮箱本身和一个错误对象。

虽然这样使用Error实例是合理合法的,但在实际应用中都是在异常处理中,我们来详细看一下:

2.使用try catch 处理异常

try…catch的思想是:首先尝试做一些事情,如果出错了则捕获这些异常。

例如,如果给validateEmail传一个非字符串的值,那么在调用match方法的时候就会报错,此时就需要捕获异常的逻辑。看一下如下代码:

function validateEmail(email) {
    return email.match(/@/) ? email : new Error('此邮箱非法')
}

const testEmail = null

try{
    const res = validateEmail(testEmail)
    if(res instanceof Error) {
        console.error(`Error: ${res.message}`)
    } else {
        console.log(res)
    }
} catch(err) {
    console.error(`Error: ${err.message}`)
}

运行结果:

图片

在这段代码中传给validateEmail的值testEmail是一个空值,调用match方法的时候就会报错,但是我们在catch语句中对错误进行了捕获,所以程序就不会崩溃,而是将错误日志打印出来。

注意:如果try语句块中没有错误,那么catch语句块中的语句不会执行。

在上面的例子中,使用了try…catch捕获了JS产生的异常,如果不想捕获,也可以向上一层‘抛出异常’,我们来看一下:

3.使用throw抛出异常

如下代码所示:

const error = new Error('非法的邮件')
function validateEmail(email) {
    return email.match(/@/) ? email : new Error('此邮箱非法')
}
const testEmail = null
function testThrow(){
    const res = validateEmail(testEmail)
    if(res instanceof Error) {
        throw Error('我是被抛出来的错误信息')
    } else {
        console.log(res)
    }
}
try{
    testThrow()
} catch(err) {
    console.error(`Error: ${err.message}`)
}

 

图片

 

我们期望的是 输出 ‘Error:我是被抛出来的错误信息’,但是为什么不是呢?这是因为在调用validateEmail的时候已经报错了,所以程序不会执行后面的if判断逻辑了。我们可以改一下代码:

const error = new Error('非法的邮件')

function validateEmail(email) {
    return email.match(/@/) ? email : new Error('此邮箱非法')
}

const testEmail = '@'

function testThrow(){
    const res = validateEmail(testEmail)
    if(res.length === 1) {
        throw Error('我是被抛出来的错误信息')
    } else {
        console.log(res)
    }
}

try{
    testThrow()
} catch(err) {
    console.error(`Error: ${err.message}`)
}

程序运行结果如下图所示:

图片

在上面的代码中,我们把测试邮箱改为只含有一个‘@’的字符串,在if判断逻辑中增加判断,如果邮箱的长度是1,则向外层抛出一个错误。

 

注意:调用throw的时候,代码会立即停止执行。

很多的时候,try块中含有对一些资源的引用,比如http链接或者文件之类的资源。不管有没有发生错误,总是要释放这些资源,防止应用程序永远占用这些资源。

由于try语句块中处处都可能发生异常,所以在try块中释放资源并不安全。此外,由于catch块中的代码只有在发生错误的时候才会执行,所以在catch块中释放资源也不合适;这时,finally就派上用场了。我们来通过示例代码理解一下

4.try…catch…finally

try{
    // 假设这块有引用资源
    console.log('try ...')
    throw Error('bug')
} catch(err) {
    console.log('bug')
} finally {
    console.log('always run')
}

运行结果如下:

图片

下面我们把throw Error的代码去掉,再看一下:

try{
    // 假设这块有引用资源
    console.log('try ...')
} catch(err) {
    console.log('bug')
} finally {
    console.log('always run')
}

运行结果:

图片

 

通过以上两段代码的运行结果我们看到了,不管有没有异常finally语句中的代码都会被执行。

在实际的应用程序中会调用很多函数,而这些被调用的函数又会调用其他的函数。例如函数 a 调用了函数b,函数b又调用了函数c。当函数c正在执行的时候,函数b没有完成,函数a也没有完成。我们把这种没有完成的嵌套函数调用称之为调用栈。下面我们看一下调用栈中的异常处理:

5.异常处理和调用栈

如果在函数c中出错,那么函数b也会出错,函数a也会出错。换言之,错误会沿着调用栈传递,直到错误被捕获。

错误可以在调用栈的任意级别被捕获,如果他们没有被捕获,JavaScript解释器就会强制终止程序。这种没有被捕获的异常会导致程序崩溃。当异常被捕获后,调用栈会提供一些用于诊断错误的有用信息,可以帮助我们诊断错误的原因,定位错误的出处。我们来看一个例子:

function a() {
    console.log('a调用了b');
    b();
    console.log('a结束');
}
function b() {
    console.log('b调用了c');
    c();
    console.log('b结束');
}
function c() {
    console.log('c抛出错误');
    throw new Error('C 出错了');
    console.log('c结束') //VSCode 提示:Unreachable code detected
}
function d(){
    console.log('d调用了c');
    c();
    console.log('d结束');
}
try {
    a();
} catch(err) {
    console.log(err.stack)
}
try {
    d();
} catch(err) {
    console.log(err.stack)
}

程序运行的效果如图所示:

图片

出现at的地方就是调用栈的轨迹,会从最深层的调用开始,直到没有函数调用为止。这里一共有两个调用栈轨迹:一个表明在b中调用了c,在a中又调用了b;另一个显示在d中调用了c。

以上就是我们今天重温的主要内容,要知道一旦出现异常,就一定要捕获它,除非你想让程序崩溃或者对之视而不见。来一张图总结我们今天学习的内容:

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重温新知

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值