错误捕获

错误对象

请大家运行如下代码

let a = un * 5
console.log('后续代码执行了吗?')

就会发现 js引擎 显示如下的错误提示

Uncaught ReferenceError: un is not defined

大家要学会看 js引擎 的报错。

这就是 js引擎 向我们报告, 有一个 ReferenceError错误对象 产生了。

这个 ReferenceError 代表的是一个 未定义变量 un 这样的错误 。

因为这个问题, js引擎 没有办法继续执行后面的代码了,所以后面的打印语句没有执行到。


ReferenceError 就是一个错误对象的类型(实际上是一个构造函数),继承自(原型是) Error 类型(实际上是一个构造函数)。



js 还有其他的错误类型, 都是继承自 Error 类型,代表各种不同类型的错误。

大家可以在打开浏览器控制台,输入如下代码:

// Uncaught SyntaxError: Invalid left-hand side in assignment
8 = 9
console.log('后续代码执行了吗?')

SyntaxError 是 表示语法错误的对象


// Uncaught TypeError: 4355 is not a function
4355()
console.log('后续代码执行了吗?')

TypeError 是 表示类型错误的对象

抛出错误

前面的示例是 js 引擎 抛出错误。

我们编写的 js 代码也可以抛出错误。

比如,我们要开发程序,实现一个把用户输入的路程长度从英里换算成公里,如下所示

var miles, fmiles,km
miles = prompt('请输入英里数:')
fmiles = parseFloat(miles) 
if (isNaN(fmiles))
  throw new Error('输入的必须是数字')

km = fmiles * 1.609344
console.log(`${miles} 英里等于 ${km} 公里`)

抛出错误, 使用关键字 throw ,后面加一个错误对象,这里是新构建一个对象,直接使用 Error 构造函数创建。

Error 构造函数的参数会作为 error对象的message属性,用来描述此处具体的错误信息,比如原因。

执行完 throw 抛出异常的代码后, 后续的代码不会再执行


当然我们也可以抛出更有针对性意义的 Error 子类型 ,比如 TypeError

var miles, fmiles, km
miles = prompt('请输入英里数:')
fmiles = parseFloat(miles) 
if (isNaN(fmiles))
  throw new TypeError('输入的必须是数字')

km = fmiles * 1.609344
console.log(`${miles} 英里等于 ${km} 公里`)

我们甚至可以抛出非Error类型的对象,比如

if (isNaN(fmiles))
  throw {code : 401,info : '输入的必须是数字'}

捕获错误

js引擎 执行代码过程中,如果发生错误,就会导致 js引擎 没法继续按照正常流程往下执行代码,所以 js引擎 会结束当前代码的执行。

有时,我们在编码的时候, 预料到了某些代码运行时可能出现某些错误,如果这些错误不至于必须结束程序,就可以使用 try... catch ... 这样的语法来捕获和处理错误。

比如,

var stockTable = {
  sh : {
    华远地产 : '600743',
    中天科技 : '600522',
  },
  sz : {
    深南股份 : '002417',
    中兴通讯 : '000063',
  },
}
while(true){ 
  let input = prompt('请输入股市代码-股票进行查询:')
  let [stock, corp] = input.split('-')
  let code = stockTable[stock][corp]
  console.log(`${stock} ${corp} 股票代码是 ${code}`)
}

编写这段代码的时候, 我们就可以预料到,可能用户会输入错误的股市代码,比如 abc-中兴通讯 ,这样会产生错误

TypeError Cannot read properties of undefined

导致整个程序中断执行。


这时,我们就可以这样写

while (true){ 
  let input = prompt('请输入股市代码-股票进行查询:')
  try{
    if(input==='exit') break
    let [stock, corp] = input.split('-')
    let code = stockTable[stock][corp]
    console.log(`${stock} ${corp} 股票代码是 ${code}`)
  } 
  catch (e){
    console.log(e.name,e.message)   
    console.error('请输入有效的股市代码')   
  }
}

try 下面缩进的花括号里面的代码可以看成是 监控区 中的代码。

执行监控区中代码时,如果出现 抛出错误, js引擎 会结束监控区中后续代码的执行,并跳转到对应的 catch 引导的花括号里面的代码段执行,不会因此中止程序。


catch 引导的代码段 就是对 错误 的一种处理

既然程序已经知道如何处理这种问题, 就不需要结束执行,只需要执行完 处理代码后, 进行原来正常的执行流程。

在这里,就是继续 while (true) 循环。



如果我们开发程序的时候,估计某个代码段中可能出现好几种类型的错误,可以使用 instanceof 判定错误类型,并进行相应的处理。

while (true){ 
  let input = prompt('请输入股市代码-股票进行查询:')
  try{
    if(input==='exit') break
    let [stock, corp] = input.split('-')
    let code = stockTable[stock][corp]
    console.log(`${stock} ${corp} 股票代码是 ${code}`)
  } 
  catch (e){
    if (e instanceof TypeError) {
      console.error('请输入有效的股市代码')
    } 
    else if (e instanceof RangeError) {
      console.error(e)
    }
    // 未知类型错误,继续抛出
    else {
      console.log('未知类型错误')
      throw e
    }
  }
}

在 catch 代码块中, 如果发现当前代码没法处理这个异常,可以使用 throw 继续往上抛出,后面会讲。


如果 try 的代码里面是自己定义抛出的错误对象, 甚至可以通过自定义的抛出对象属性判断错误种类,如下

while (true){ 
  let inNum = parseInt(prompt('请输入数字:'))
  try{
    if (inNum === 401)
      throw {code : 401,info : '输入的是401!'}
    else if (inNum === 402)
      throw {code : 402,info : '输入的是402!'}
    else 
      throw {code : 500,info : '输入的是其它!'}
  } 
  catch (e){
    if (e.code === 401) 
      console.log('401 错误处理')
    else if (e.code === 402)
      console.log('402 错误处理')
    // 未知类型错误,继续抛出
    else {
      console.log('未知类型错误')
      throw e
    }
  }
}

嵌套捕获

try 捕获错误可以层层嵌套,比如

try{

  var inNum = 401
  try {
    
    if (inNum === 401)
      throw {code : 401,info : '输入的是401!'}
    else 
      throw {code : 500,info : '输入的是其它!'}

  } 
  catch (e) {
    if (e.code === 401) 
      console.log('401 错误处理')
    else {
      console.log('未知类型错误')
      throw e
    }
  }
}
catch (e) {
  console.log('处理内层代码不适合处理的错误')
}

当内层代码抛出异常,优先会被 内层 的 catch 捕获, 所以不会执行到外层的 catch 代码块中。(inNum = 401 的情况)

如果当内层catch代码发现不适合处理,又 throw 抛出, 就会被 外层的 catch 捕获进行处理。(inNum = 500 的情况)

函数调用里面产生的错误

前面我们的例子都是在 js 函数外面的主体部分代码 抛出的错误对象。

如果一个错误对象 在函数里面抛出 ,会发生什么事呢?

当一个函数里面产生错误对象时, 会优先使用当前函数里面捕获处理错误的 try catch 。

如果没有捕获到(比如没有 try catch), 就 会把错误对象 抛到 调用该函数的外层代码处, 查看是否有相应的 try catch

如下

function throwError(inNum) {
  if (inNum === 401)
    throw {code : 401,info : '输入的是401!'}
  else 
    throw {code : 500,info : '输入的是其它!'}
}


try {
  throwError(401); // 500
} 
catch (e) {
  if (e.code === 401) {
    console.log('401 错误处理')
  } 
  else {
    console.log('未知类型错误')
    throw e
  }
}
console.log('后续代码执行')

上例中,函数 throwError 里面没有 捕获异常的代码, 所以会被抛出到调用函数的代码

throwError(401);

这行代码是 被 try catch 保护处理的, 异常会被捕获, 所以函数里面抛出的异常,在函数外面的 catch 被捕获处理了。

函数调用栈抛出的错误

代码1

大家来看下面的一段代码:

function level_3(){
  console.log ('进入 level_3')
  throw {info : '自定义错误对象'}
  console.log ('离开 level_3')
}

function level_2(){
  console.log ('进入 level_2')
  level_3()
  console.log ('离开 level_2')
}

function level_1(){
  console.log ('进入 level_1')
  level_2()
  console.log ('离开 level_1')
}


level_1()

console.log('程序正常退出') 

运行该代码会得到类似下面的结果

进入 level_1
进入 level_2
进入 level_3
Uncaught { info: '自定义错误对象' }
level_3	@	VM3385:3
level_2	@	VM3385:9
level_1	@	VM3385:15
(anonymous)	@	VM3385:20

函数调用次序是这样的

主体部分调用 函数 level_1

函数level_1调用 函数level_2

函数level_2调用 函数level_3

大家注意:函数 level_3 中有个 抛出的错误。

所以执行到该函数的时候, js引擎 报错了。

它还显示了错误代码的具体位置。 也就是

level_3	@	VM3385:3
level_2	@	VM3385:9
level_1	@	VM3385:15
(anonymous)	@	VM3385:20

这里说明了这行引起错误的代码, 是怎样被 一层层 的调用进来的。

这就是函数调用栈的信息。


当错误在函数中产生的时候, js引擎 会终止当前代码的执行, 查看当前函数是否 声明了该类型错误的 catch 处理,如果有,就执行, 随后继续执行代码。

如果当前函数没有 声明了该类型错误的处理, 就会中止当前函数的执行,退出到调用该函数的上层函数中, 查看上层是否有 声明了该类型错误的 catch 处理。如果有,就执行该错误匹配处理。 随后继续执行代码。

如果上层函数也没有 该类型错误的匹配处理, 就会到继续到再上层的函数查看是否有 该类型错误的匹配处理。

如此这般,直到到了最外层的代码。 如果依然没有 声明了该类型错误处理,就终止当前代码的执行。


下面的几个示例代码,分别在不同的函数调用层次 捕获错误。 大家可以依次执行一下,看看各自对执行结果有什么影响。

代码2

function level_3(){
  console.log ('进入 level_3')
  throw {info : '自定义错误对象'}
  console.log ('离开 level_3')
}

function level_2(){
  console.log ('进入 level_2')
  level_3()
  console.log ('离开 level_2')
}

function level_1(){
  console.log ('进入 level_1')
  level_2()
  console.log ('离开 level_1')
}


try{
  level_1()
}
catch (e) {
  console.log('错误处理')
}

console.log('程序正常退出')

代码3

function level_3(){
  console.log ('进入 level_3')
  throw {info : '自定义错误对象'}
  console.log ('离开 level_3')
}

function level_2(){
  console.log ('进入 level_2')
  level_3()
  console.log ('离开 level_2')
}

function level_1(){
  console.log ('进入 level_1')
  try{
    level_2()
  }
  catch (e) {
    console.log('错误处理')
  }
  console.log ('离开 level_1')
}


level_1()

console.log('程序正常退出')

代码4

function level_3(){
  console.log ('进入 level_3')
  throw {info : '自定义错误对象'}
  console.log ('离开 level_3')
}

function level_2(){
  console.log ('进入 level_2')
  try{
    level_3()
  }
  catch (e) {
    console.log('错误处理')
  }
  console.log ('离开 level_2')
}

function level_1(){
  console.log ('进入 level_1')
  level_2()
  console.log ('离开 level_1')
}


level_1()

console.log('程序正常退出')

代码5

function level_3(){
  console.log ('进入 level_3')
  try{
    throw {info : '自定义错误对象'}
  }
  catch (e) {
    console.log('错误处理')
  }
  console.log ('离开 level_3')
}

function level_2(){
  console.log ('进入 level_2')
  level_3()
  console.log ('离开 level_2')
}

function level_1(){
  console.log ('进入 level_1')
  level_2()
  console.log ('离开 level_1')
}


level_1()

console.log('程序正常退出')

finally

try ... catch 后面还可以跟一个 finally 代码块。

finally 代码块的代码, 不管try 里面有无错误抛出,都要执行的。

比如

var inNum = 500 
try {
  if (inNum === 401)
    throw {code : 401,info : '输入的必须是数字'}
} 
catch (e) {
  console.log('错误处理')
}
finally {
  console.log('不管有无错误,都要做的处理')
}
console.log('后续代码')

特别是在没有catch的时候有用,如下:

try {
  throw {code : 401,info : '输入的必须是数字'}
} 
finally {
  console.log('不管有无错误,都要做的处理')
}
console.log('后续代码')

您需要高效学习,找工作? 点击咨询 报名实战班

点击查看学员就业情况