「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。
函数的错误处理方案
我们实现一个函数,这个函数返回两数之和。
function sum(num1, num2) {
return num1 + num2
}
然而,函数调用者会传递各种类型的参数,而且函数返回的结果也不会告知调用者参数类型错了。
console.log(sum({name: 'haha'}, true));
有小伙伴说了,我们可以在函数中对参数进行类别的判断,如果类型不是number类型,就return。
function sum(num1, num2) {
// return num1 + num2
if(typeof num1 !== 'number' || typeof num2 !== 'number') {
return
}
return num1 + num2
}
后续我们调用这个函数,返回给我们的是undefined。然而,这样还是有一个缺点,如果函数内部逻辑复杂,我们调用这个函数得到的undefined不知道是由于函数本身应该返回给我们的值,还是由于我们调用函数的参数类型发生错误。
事实上,正确的做法应该是如果没有通过某些认证,应该让外界知道函数内部报错了。
通过throw关键字抛出一个异常,可以让一个函数告知外界自己内部出现了错误。
function sum(num1, num2) {
// return num1 + num2
if(typeof num1 !== 'number' || typeof num2 !== 'number') {
// return
throw 'parameters is error type'
}
return num1 + num2
}
console.log(sum({name: 'haha'}, true));
console.log('后续代码');
我们可以看到当我们传递的参数不符合要求时,就会报错,如果不对错误进行处理,后续代码就不执行了。
throw关键字
throw关键字后面可以跟上一个表达式来表示具体的异常信息。
throw关键字后面可以跟基本数据类型,如string、number、boolean等。除了基本数据类型,还可以跟对象类型。对象类型跟基本数据类型相比,优势在于可以表达更多的信息,包括错误码、错误信息等。
function foo(type) {
console.log('函数开始执行');
if(type === 0) {
// throw 'error'
// throw 0
throw {errorCode: -1001, errorMessage: 'type不能为0'}
}
console.log('函数结束执行');
}
foo(0)
console.log('后续代码');
但是每次写这么长的对象又有点麻烦,我们可以创建一个类。
class MyError {
constructor(errorCode, errorMessage) {
this.errorCode = errorCode
this.errorMessage = errorMessage
}
}
function foo(type) {
console.log('函数开始执行');
if(type === 0) {
// throw 'error'
// throw 0
// throw {errorCode: -1001, errorMessage: 'type不能为0'}
throw new MyError(-1001, 'type不能为0')
}
console.log('函数结束执行');
}
foo(0)
console.log('后续代码');
事实上,javascript给我们提供了一个Error类,我们可以直接通过new创建这个类的对象,并且直接传入错误信息。它与上面我们自己创建的类不同的是,它提供了调用栈信息。需要注意的是,如果函数中抛出了异常,那么函数内后续的代码都不会继续执行。
function foo(type) {
console.log('函数开始执行');
if(type === 0) {
// throw 'error'
// throw 0
// throw {errorCode: -1001, errorMessage: 'type不能为0'}
// throw new MyError(-1001, 'type不能为0')
throw new Error('type不能为0')
}
console.log('函数结束执行');
}
Error包含三个属性:
- message: 创建Error对象时传入的message
- name: Error的名称,通常和类的名称一致
- stack: 整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack
const err = new Error('type不能为0')
console.log(err.message);
console.log(err.name);
console.log(err.stack);
throw err
Error有一些自己的子类:
- RangeError: 下标值越界时使用的错误类型
- SyntaxError: 解析语法错误时使用的错误类型
- TypeError: 出现类型错误时使用的错误类型
const err = new TypeError('type不能为0')
throw err
对抛出的异常进行处理
在下面代码中,我们在foo函数中抛出了一个异常,bar函数调用foo函数,test函数调用bar函数,一层层调用,最后在全局调用demo函数。
function foo(type) {
if(type === 0) {
throw new Error('foo错误信息')
}
}
function bar() {
foo(0)
}
function test() {
bar()
}
function demo() {
test()
}
demo()
console.log('后续代码');
我们在foo函数中抛出一个错误,可以很清楚地看到打印出来的调用栈。
对抛出的异常,我们有两种方式进行处理:不处理和使用try...catch对异常进行捕获。
(1)不处理
如果我们在调用一个函数时,这个函数抛出了异常,但是我们没有对这个异常进行处理,那么这个异常会继续传递到上一个函数调用中。如果在最顶层也没有对这个异常进行处理,我们程序就会终止运行,并且报错。
在我们的代码中,foo函数抛出了异常,在bar函数中调用了foo函数,bar函数会拿到这个异常。但是bar函数没有对这个异常进行处理,那么这个异常会被继续传递到调用bar函数的函数,也就是test函数。现在test函数有了这个异常,但是它依然没有对这个异常进行处理,那么这个异常会被继续传递到调用test函数的函数,也就是demo函数。现在demo函数有了这个异常,但是它依然没有对这个异常进行处理,那么这个异常会被继续传递到我们的全局代码逻辑中。全局依然没有处理这个异常,这个时候程序会终止执行,后续代码都不会再执行。
(2)使用try...catch来捕获异常
在很多情况下,当出现异常时,我们不希望程序直接退出,而是希望正确地处理异常。这时我们可以使用try...catch来捕获异常。
我们可以将可能出现的异常放在try代码块中,那么如果抛出一个异常,可以在catch中捕获到,并且err参数能够获取到传递过来的错误信息。
function bar() {
try {
foo(0)
} catch(err) {
console.log('err', err);
}
}
我们可以看到使用try...catch不影响后续代码的执行。
如果我们不想看到详细的调用栈等错误信息,我们可以使用err.message。
try {
foo(0)
} catch(err) {
// console.log('err',err);
console.log('err', err.message);
}
另外,我们可以不在调用foo的位置进行try...catch,可以在调用bar的位置进行try...catch,甚至可以在调用demo的位置,也就是全局进行try...catch。
function bar() {
foo(0)
}
调用bar的位置进行try...catch
function test() {
try {
bar()
} catch(err) {
console.log('err', err);
}
}
全局进行try...catch
try {
demo()
} catch(err) {
console.log('err', err);
}
console.log('后续代码');
需要补充的是,在ES10中,catch后面绑定的error可以省略。
try {
demo()
} catch {
console.log('出现错误啦');
}
console.log('后续代码');
如果有一些必须要执行的代码,我们可以使用finally来执行。finally不管有没有异常,都会执行里面的代码。
发生异常执行finally
try {
foo(0)
} catch(err) {
// console.log('err',err);
console.log('err', err.message);
} finally {
console.log('finally代码执行');
}
不发生异常执行finally
try {
// foo(0)
foo(1)
} catch(err) {
// console.log('err',err);
console.log('err', err.message);
} finally {
console.log('finally代码执行');
}
注意:如果try和finally中都有返回值,那么会使用finally当中的返回值。