javascript中对异常的处理

461 阅读6分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」。

函数的错误处理方案

我们实现一个函数,这个函数返回两数之和。

function sum(num1, num2) {
  return num1 + num2
}

然而,函数调用者会传递各种类型的参数,而且函数返回的结果也不会告知调用者参数类型错了。

console.log(sum({name: 'haha'}, true));

image.png 有小伙伴说了,我们可以在函数中对参数进行类别的判断,如果类型不是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('后续代码');

我们可以看到当我们传递的参数不符合要求时,就会报错,如果不对错误进行处理,后续代码就不执行了。

image.png

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('后续代码');

image.png 但是每次写这么长的对象又有点麻烦,我们可以创建一个类。

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('函数结束执行');
}

image.png 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函数中抛出一个错误,可以很清楚地看到打印出来的调用栈。 image.png 对抛出的异常,我们有两种方式进行处理:不处理和使用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不影响后续代码的执行。 image.png 如果我们不想看到详细的调用栈等错误信息,我们可以使用err.message

  try {
    foo(0)
  } catch(err) {
    // console.log('err',err);
    console.log('err', err.message);
  }

image.png 另外,我们可以不在调用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代码执行');
    }

image.png 不发生异常执行finally

    try {
      // foo(0)
      foo(1)
    } catch(err) {
      // console.log('err',err);
      console.log('err', err.message);
    } finally {
      console.log('finally代码执行');
    }

image.png 注意:如果try和finally中都有返回值,那么会使用finally当中的返回值。