错误优先的回调


Node.js 核心 API 暴露的大多数异步方法都遵循称为错误优先回调的惯用模式。 使用这种模式,回调函数作为参数传给方法。 当操作完成或出现错误时,回调函数将使用 Error 对象(如果有)作为第一个参数传入。 如果没有出现错误,则第一个参数将作为 null 传入。

const fs = require('node:fs');

function errorFirstCallback(err, data) {
  if (err) {
    console.error('There was an error', err);
    return;
  }
  console.log(data);
}

fs.readFile('/some/file/that/does-not-exist', errorFirstCallback);
fs.readFile('/some/file/that/does-exist', errorFirstCallback);

JavaScript try…catch 机制不能用于拦截异步 API 产生的错误。 初学者的一个常见错误是尝试在错误优先的回调中使用 throw

// 这行不通:
const fs = require('node:fs');

try {
  fs.readFile('/some/file/that/does-not-exist', (err, data) => {
    // 错误的假设:在这里抛出...
    if (err) {
      throw err;
    }
  });
} catch (err) {
  // 这不会捕获抛出的错误!
  console.error(err);
}

这不起作用,因为传给 fs.readFile() 的回调函数是异步调用的。 当回调被调用时,周围的代码(包括 try…catch 块)已经退出。 大多数情况下,在回调中抛出错误会使 Node.js 进程崩溃。 如果启用了,或者已经在 process.on('uncaughtException') 注册了句柄,则可以拦截此类错误。

Most asynchronous methods exposed by the Node.js core API follow an idiomatic pattern referred to as an error-first callback. With this pattern, a callback function is passed to the method as an argument. When the operation either completes or an error is raised, the callback function is called with the Error object (if any) passed as the first argument. If no error was raised, the first argument will be passed as null.

const fs = require('node:fs');

function errorFirstCallback(err, data) {
  if (err) {
    console.error('There was an error', err);
    return;
  }
  console.log(data);
}

fs.readFile('/some/file/that/does-not-exist', errorFirstCallback);
fs.readFile('/some/file/that/does-exist', errorFirstCallback);

The JavaScript try…catch mechanism cannot be used to intercept errors generated by asynchronous APIs. A common mistake for beginners is to try to use throw inside an error-first callback:

// THIS WILL NOT WORK:
const fs = require('node:fs');

try {
  fs.readFile('/some/file/that/does-not-exist', (err, data) => {
    // Mistaken assumption: throwing here...
    if (err) {
      throw err;
    }
  });
} catch (err) {
  // This will not catch the throw!
  console.error(err);
}

This will not work because the callback function passed to fs.readFile() is called asynchronously. By the time the callback has been called, the surrounding code, including the try…catch block, will have already exited. Throwing an error inside the callback can crash the Node.js process in most cases. If domains are enabled, or a handler has been registered with process.on('uncaughtException'), such errors can be intercepted.