Node.js程序异常处理

使用throw关键字抛出异常

throw 关键字用于抛出一个异常,它可以在特定的情形下自行抛出异常。throw 语句的基本格式如下:

throw value

参数 value 表示抛出的异常,它的值可以是任何 JavaScript 类型的值(包括字符串、数字或对象等)。例如,在 JavaScript 代码中使用下面代码抛出不同类型的异常都是合法的:

throw "程序出错了";  //抛出了一个值为字符串的异常
throw 1;             //抛出了一个值为整数1的异常
throw true;          //抛出了一个值为true的异常

但在 Node.js 中,通常不抛出这些类型的值,而是抛出 Error 对象,例如下面的代码:

throw new Error('程序出错了')

Error错误对象

上面讲到,在 Node.js 中通常使用 throw 抛出 Error 对象,那么 Error 对象是什么呢?

Error 对象是一个错误对象,它由 Error 核心模块提供,当使用 Error 对象时,并不表明错误发生的具体情况,它会捕获堆栈跟踪,并提供所发生错误的描述内容。Error 对象的使用方法如下:

new Error(message)

参数 message 表示要显示的错误信息。

Error 对象提供了一些属性,用于获取错误相关的信息,分别如下。

  • name 属性:获取错误的类型名称,比如内置错误类型 TypeError 等。

  • message 属性:获取错误信息。

  • stack 属性:获取代码中 Error 被实例化的位置。

Error 类是 Node.js 中所有错误类的基类,其常用子类及说明如表 17.1 所示。

image 2024 04 17 23 58 04 866
Figure 1. 表17.1 Error 类的常用子类及说明

例如,下面代码定义了一个代码块,其中通过实例化 Error 对象创建了一个异常,并使用 throw 关键字抛出该异常:

let syncError = () => {
    throw new Error('自定义异常')
}

使用try…​catch语句捕获异常

异常定义之后,需要在程序中捕获,这时需要使用 try…catch 语句。try…catch 语句允许在 try 后面的大括号 {} 中放置可能发生异常情况的程序代码,以对这些代码进行监控;在 catch 后面的大括号 {} 中放置处理异常的程序代码。try…catch 语句的基本语法如下:

try {
               //可能会出错的代码,出错时抛出一个错误
} catch (e) {
               //处理异常的代码
}

参数 e 表示捕获的异常。

例如,下面代码使用 try…catch 捕获 17.3.2 节示例代码中抛出的异常信息:

try {
     syncError()
} catch (e) {
     console.log(e.message)
}
console.log('异常被捕获')

程序运行如下:

自定义异常
异常被捕获

在开发程序时,如果遇到需要处理多种异常信息的情况,可以在一个 try 代码块后面跟多个 catch 代码块,这里需要注意的是,如果使用了多个 catch 代码块,则 catch 代码块中的异常类顺序是先子类后父类。

完整的异常处理语句应该包含 finally 代码块,通常情况下,无论程序中有无异常产生,finally 代码块中的代码都会被执行,其语法格式如下:

try{
            //可能会出错的代码,出错时抛出一个错误
}catch(e){
            //处理异常的代码
}finally{
            //最终执行的代码
}

使用 try…catch…finally 语句时,不管 try 代码块内有没有抛出异常,finally 代码块总会被执行。如果 try 代码块内发生错误,finally 代码块将在 catch 代码块之后被执行;如果没有发生错误,将跳过 catch 代码块,直接执行 finally 代码块中的代码。

使用异常处理语句时,可以不写 catch 代码块,比如写成 try…​finally 的形式,但需要注意的是,try 代码块后必须至少跟一个 catchfinally 代码块,不能只写 try。例如,使用同步方式读取一个文件,并使用 try…catch 语句捕获文件不存在错误,最后在 finally 代码块内输出 “执行完毕” 的提示,代码如下:

const fs=require("fs")
try{
     var data = fs.readFileSync("test.txt", {"encoding":"utf8"})
} catch (err) {
     console.log("文件不存在")
     throw err;
} finally {
     console.log("执行完毕!")
}

运行结果如下:

文件不存在
执行完毕!
ENOENT: no such file or directory, open 'test.txt'

异步程序中的异常处理

前面讲解的是同步程序的异常捕获,如果是异步程序出现异常,该如何捕获呢?例如,下面是一个异步代码块,其中抛出了一个异常,代码如下:

//模拟异步代码块内出现异常
let asyncError = () => {
     setTimeout(function () {
          throw new Error('异步异常')
     }, 100)
}

如果我们使用传统的 try…catch 捕获上面异步代码中抛出的异常,则写法应该如下:

(async function () {
     try {
          await asyncError()
     } catch (e) {
          console.log(e.message)  //处理异常
     }
})()

但在运行程序时,却出现了如图 17.12 所示的结果。

image 2024 04 18 00 05 40 196
Figure 2. 图17.12 使用传统的 try…catch 捕获异步代码中的异常时的错误信息

通过上面示例代码可以看出,异步代码中的异常是无法使用 try…catch 方法捕获的,那么,如何捕获异步程序中的异常呢?Node.js 中提供了两种方法,用于捕获异步程序中的异常,分别如下。

  • process 方式。process 模块是 Node.js 提供给开发者用来和当前进程进行交互的工具,通过监听它的 uncaughtException 事件,可以处理所有未被捕获的异常,包括同步代码块中的异常和异步代码块中的异常。例如,下面代码用来捕获本节开始定义的异步代码中的异常:

    process.on('uncaughtException', function (e) {
         console.log(e.message)                     //处理异常
    });
    asyncError()
  • domain 方式。通过监听 domain 模块的 error 事件来处理异步代码块中的异常,domain 模块主要用来简化异步代码的异常处理,它可以处理 try…catch 无法捕捉的异常。例如,下面代码使用 domain 方式捕获本节前面定义的异步代码中的异常:

    let domain = require('domain')
    let d = domain.create()
    d.on('error', function (e) {
         console.log(e.message)     //处理异常
    })
    d.run(asyncError)

使用 process 方式和 domain 方式都可以捕获异步代码块中的异常,但 process 方式只适用于记录异常信息的场合,因此,在捕获异步代码库中的异常时,推荐使用 domain 方式。