错误的传播和拦截


Node.js 支持多种机制来传播和处理应用程序运行时发生的错误。 如何报告和处理这些错误完全取决于 Error 的类型和调用的 API 的风格。

所有的 JavaScript 错误都作为异常处理,使用标准的 JavaScript throw 机制立即生成并抛出错误。 这些是使用 JavaScript 语言提供的 try…catch 构造处理的。

// 由于 z 未定义,因此抛出 ReferenceError。
try {
  const m = 1;
  const n = m + z;
} catch (err) {
  // 在此处理错误。
}

任何使用 JavaScript throw 机制都会引发异常,必须使用 try…catch 处理,否则 Node.js 进程将立即退出。

除了少数例外,同步的 API(任何不接受 callback 函数的阻塞方法,例如 fs.readFileSync)都使用 throw 来报告错误。

异步的 API 中发生的错误可以以多种方式报告:

  • 大多数接受 callback 函数的异步方法将接受作为第一个参数传给该函数的 Error 对象。 如果第一个参数不是 null 并且是 Error 的实例,则发生了应该处理的错误。

    const fs = require('node:fs');
    fs.readFile('a file that does not exist', (err, data) => {
      if (err) {
        console.error('There was an error reading the file!', err);
        return;
      }
      // 否则处理数据
    });
  • 当在 EventEmitter 对象上调用异步方法时,错误可以路由到该对象的 'error' 事件。

    const net = require('node:net');
    const connection = net.connect('localhost');
    
    // 向流中添加 'error' 事件句柄:
    connection.on('error', (err) => {
      // 如果连接被服务器重置,
      // 或者根本无法连接,或者连接遇到任何类型的错误,
      // 则错误将发送到这里。
      console.error(err);
    });
    
    connection.pipe(process.stdout);
  • Node.js API 中的一些典型的异步方法可能仍然使用 throw 机制来引发必须使用 try…catch 处理的异常。 没有此类方法的完整列表;请参阅每种方法的文档以确定所需的适当错误处理机制。

'error' 事件机制的使用最常见于基于流基于事件触发器的 API,其本身代表了一系列随时间推移的异步操作(而不是单个操作可能通过或失败)。

对于所有的 EventEmitter 对象,如果未提供 'error' 事件句柄,则将抛出错误,导致 Node.js 进程报告未捕获的异常并崩溃,除非:domain 模块使用得当或已为 'uncaughtException' 事件注册句柄。

const EventEmitter = require('node:events');
const ee = new EventEmitter();

setImmediate(() => {
  // 这将导致进程崩溃,
  // 因为没有添加 'error' 事件句柄。
  ee.emit('error', new Error('This will crash'));
});

以这种方式产生的错误不能使用 try…catch 拦截,因为其抛出后调用代码已经退出。

开发者必须参考每种方法的文档,以确定这些方法引发的错误是如何传播的。

Node.js supports several mechanisms for propagating and handling errors that occur while an application is running. How these errors are reported and handled depends entirely on the type of Error and the style of the API that is called.

All JavaScript errors are handled as exceptions that immediately generate and throw an error using the standard JavaScript throw mechanism. These are handled using the try…catch construct provided by the JavaScript language.

// Throws with a ReferenceError because z is not defined.
try {
  const m = 1;
  const n = m + z;
} catch (err) {
  // Handle the error here.
}

Any use of the JavaScript throw mechanism will raise an exception that must be handled using try…catch or the Node.js process will exit immediately.

With few exceptions, Synchronous APIs (any blocking method that does not accept a callback function, such as fs.readFileSync), will use throw to report errors.

Errors that occur within Asynchronous APIs may be reported in multiple ways:

  • Most asynchronous methods that accept a callback function will accept an Error object passed as the first argument to that function. If that first argument is not null and is an instance of Error, then an error occurred that should be handled.

    const fs = require('node:fs');
    fs.readFile('a file that does not exist', (err, data) => {
      if (err) {
        console.error('There was an error reading the file!', err);
        return;
      }
      // Otherwise handle the data
    });
  • When an asynchronous method is called on an object that is an EventEmitter, errors can be routed to that object's 'error' event.

    const net = require('node:net');
    const connection = net.connect('localhost');
    
    // Adding an 'error' event handler to a stream:
    connection.on('error', (err) => {
      // If the connection is reset by the server, or if it can't
      // connect at all, or on any sort of error encountered by
      // the connection, the error will be sent here.
      console.error(err);
    });
    
    connection.pipe(process.stdout);
  • A handful of typically asynchronous methods in the Node.js API may still use the throw mechanism to raise exceptions that must be handled using try…catch. There is no comprehensive list of such methods; please refer to the documentation of each method to determine the appropriate error handling mechanism required.

The use of the 'error' event mechanism is most common for stream-based and event emitter-based APIs, which themselves represent a series of asynchronous operations over time (as opposed to a single operation that may pass or fail).

For all EventEmitter objects, if an 'error' event handler is not provided, the error will be thrown, causing the Node.js process to report an uncaught exception and crash unless either: The domain module is used appropriately or a handler has been registered for the 'uncaughtException' event.

const EventEmitter = require('node:events');
const ee = new EventEmitter();

setImmediate(() => {
  // This will crash the process because no 'error' event
  // handler has been added.
  ee.emit('error', new Error('This will crash'));
});

Errors generated in this way cannot be intercepted using try…catch as they are thrown after the calling code has already exited.

Developers must refer to the documentation for each method to determine exactly how errors raised by those methods are propagated.