JS中的错误处理方案

128 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

错误的处理

在实际开发中我们会自己封装一些工具函数,并给其它人使用:

  • 其它人在使用的时候,可能会传递一些参数;
  • 对于函数来说,需要对这些参数进行验证,否则可能得到意外的结果;

很多时候,当我们验证到传入参数并不符合要求时,就会直接return;

但是return存在很大的弊端:

  • 调用者并不清楚函数到底有没有正确执行(可能函数的执行结果就是一个undefined);
  • 事实上,正确的做法应该是没有通过某些验证时,应该让外界知道函数内部报错了;

如果让一个函数告知外界自己内部出现错误了呢?

  • 通过throw语句,抛出一个异常;

throw语句:

  • throw语句用于抛出一个用户自定义的异常;
  • 当执行到throw语句时,当前函数就会终止执行(throw后面的语句不会被执行);

如果我们执行代码的之后遇到报错,拿到错误信息时我们就可以及时地去修正代码;

// 本意是对两个数字进行求和
function numberSum(num1, num2) {
  return num1 + num2
}

numberSum('aaa', 'bbb')
// 在调用的时候传入参数类型变成了字符串,
// 导致函数的执行结果变成了字符串

function numberSum2(num1, num2) {
  if (typeof num1 === 'number' && typeof num2 === 'number') {
    return num1 + num2
  } else {
    throw 'arguments expect number type'
  }
}

numberSum2('aaa', 'bbb')

执行结果:

throw关键字

throw表达式就是在throw后面可以跟上一个表达式来表示具体的异常信息:throw expression

throw关键字可以跟上哪些类型呢?

  • 基本数据类型:比如number,string,boolean;
  • 对象类型:对象类型可以包含更多的信息;
function foo(type) {
  if (type === 0) {
    // throw 111
    // throw 'type error'
    // throw false
    throw {
      errCode: 1001,
      errMessage: 'type error'
    }
  }
  console.log('foo函数正常执行');
}

foo(0)

运行结果:

每次在抛出异常的时候通过字面量的方法写一个对象有点麻烦,所以我们可以创建一个错误类:

class MyError {
  constructor(code, message) {
    this.code = code
    this.message = message
  }
}

function foo(type) {
  if (type === 0) {
    // throw 111
    // throw 'type error'
    // throw false
    throw new MyError(1001, 'type error')
  }
  console.log('foo函数正常执行');
}

foo(0)

运行结果:

Error类型

事实上,JavaScript已经给我们提供了一个Error类,我们可以直接创建这个类的实例对象:

function foo(type) {
  if (type === 0) {
    throw new Error('type error')
  }
  console.log('foo正常执行');
}
foo(0)

运行结果:

Error包含三个属性:

  • messsage:创建Error对象时传入的message;
  • name:Error的名称,通常和类的名称一致;
  • stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack;

Error类还有一些自己的子类:

  • RangeError:下标值越界时使用的错误类型;
  • SyntaxError:解析语法错误时使用的错误类型;
  • TypeError:出现类型错误时,使用的错误类型;

异常的处理

我们会发现在之前的代码中,一个函数抛出了异常,调用它的时候程序会被强制终止:

  • 这是因为如果我们在调用一个函数时,这个函数抛出了异常,但是我们并没有对这个异常进行处理,那么这个异常会继续传 递到上一个函数调用中;
  • 而如果到了最顶层(全局)的代码中依然没有对这个异常的处理代码,这个时候就会报错并且终止程序的运行 ;

比如我们下面这段代码:

function bar() {
  throw Error('bar error')
}

function foo() {
  bar()
}

function baz() {
  foo()
}

baz()
console.log('后续代码执行');

它的运行结果是:

我们可以看到:

  • 在bar函数执行的时候会抛出异常,并且在bar函数内部并没有对这个异常进行处理;
  • 那么这个异常会传递到上一个函数调用中,也就是foo函数;
  • 而foo函数同样没有对异常进行处理,于是又传递给了上一层函数baz;
  • baz函数中也没有对异常进行处理,又继续传递到了全局代码中;
  • 此时依旧没有被处理,这时候程序就会终止执行,后续代码都不会执行了;

异常的捕获

很多情况下出现异常时,我们并不希望程序直接就退出执行,而是希望可以正确处理异常;

这种情况下我们可以使用try catch;

function bar() {
  throw new Error('bar error')
}

try {
  bar()
}catch(e) {
  console.log('捕获到了异常:', e);
}

console.log('后续代码的执行');

运行结果:

  • 在ES10(ES2019)中,catch后面绑定的error可以省略(当然使用这种方式我们就无法获取到具体的异常了);
  • 如果有一些必须要执行的代码,我们可以使用finally来执行:
    • finally表示最终一定会被执行的代码结构;
    • 注意:如果try和finally中都有返回值,那么会使用finally当中的返回值 ;
function foo() {
  try {
    return 'try return value'
  } catch(e) {
    console.log('foo error:', e);
  } finally {
    return 'finally return value'
  }
}

console.log(foo()); // finally return value