人人都会AI编程

8.4 工具函数:util 模块

更新时间:2026-07-11

在 Node.js 的开发中,有一些操作会反复出现:将旧式的回调风格函数转换为 Promise、深度比较两个对象、格式化字符串、判断数据类型等。Node.js 将这些高频工具封装在了内置的 util 模块中,它就像是 Node.js 内部工具箱,能有效减少重复代码,提升开发效率。

8.4.1 util.promisify —— 回调转 Promise 的利器

Node.js 早期 API 普遍采用错误优先的回调风格(callback(err, result)),这在 async/await 普及后显得不够直观。util.promisify 可以把一个遵循该约定的回调式函数转换为返回 Promise 的函数。

const util = require('util');
const fs = require('fs');

// 原始回调版本
fs.readFile('/path/to/file', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 转换为 Promise 版本
const readFilePromise = util.promisify(fs.readFile);

async function read() {
  try {
    const data = await readFilePromise('/path/to/file');
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

promisify 的原理是返回一个新函数,这个新函数内部包装了原始函数,将其回调结果转为 Promise 状态,并保持正确的 this 绑定。但需要注意,它只适用于最后一次参数为 (err, value) 回调的函数。如果函数本身有多重回调或非标准格式,则需要手动封装。

此外,为了保持类型推断,TypeScript 用户可以在 @types/node 中直接获得 fs.promises 等原生 Promises API,不必每次都 promisify。但对第三方老式模块或自定义函数,util.promisify 依然是转换的首选。

8.4.2 util.inherits —— 基于原型链的继承

在过去没有 class 语法时,util.inherits 是 Node.js 中实现类继承的常用方法。它完成了将子类原型链接到父类原型上的核心步骤。

const util = require('util');
const EventEmitter = require('events');

function MyStream() {
  EventEmitter.call(this); // 调用父类构造函数
}

util.inherits(MyStream, EventEmitter);

// 添加子类方法
MyStream.prototype.write = function(data) {
  this.emit('data', data);
};

虽然 util.inherits 至今仍然可用,但自从 ES6 classextends 关键字普及后,它已经逐渐被取代。现代 Node.js 代码中直接使用 class MyStream extends EventEmitter {} 会更清晰。不过在维护旧项目或理解一些底层模块源码时,了解 util.inherits 的存在仍有价值。

8.4.3 util.format —— 灵活的字符串格式化

util.format 提供了类似 printf 的字符串格式化功能,支持 %s(字符串)、%d(数字)、%j(JSON)等占位符。它比模板字符串更灵活的地方在于可以动态传入参数并自动处理多余参数。

const util = require('util');

// 基本格式化
const msg = util.format('Hello %s, you have %d new messages', 'Alice', 5);
// => 'Hello Alice, you have 5 new messages'

// %j 自动序列化为 JSON
const obj = { name: 'Bob', age: 30 };
const jsonMsg = util.format('User: %j', obj);
// => 'User: {"name":"Bob","age":30}'

// 参数多于占位符时,剩余参数会拼接在末尾
util.format('%d + %d = %d', 1, 2, 3, 'extra'); 
// => '1 + 2 = 3 extra'

在控制台日志输出场景中,console.log 其实内部也使用了 util.format 来处理多个参数。如果你想要构造一个包含变量值的错误消息,或者格式化日志输出,util.format 是一个很好的选择。

8.4.4 类型检查函数

util 提供了一组方便的类型判断方法,弥补了原生 typeofinstanceof 在某些场景下的不足。

  • util.types.isDate(value) —— 判断是否为 Date 对象
  • util.types.isRegExp(value) —— 判断是否为正则表达式
  • util.types.isPromise(value) —— 判断是否为原生 Promise
  • util.types.isBuffer(value) —— 判断是否为 Buffer 对象
  • util.types.isAsyncFunction(value) —— 判断是否为 async 函数
const util = require('util');

console.log(util.types.isPromise(Promise.resolve())) // true
console.log(util.types.isBuffer(Buffer.alloc(0)))    // true
console.log(util.types.isAsyncFunction(async () => {})) // true

注意,这里的类型检查位于 util.types 命名空间下,它们是比 typeof 更精确的工具。例如,typeof new Date() 返回 'object',而 util.types.isDate() 能精准识别。在处理来自外部 API 的数据或编写健壮的工具函数时,这些方法非常实用。

8.4.5 util.inspect —— 对象的深度查看

当我们需要把一个复杂对象打印成可读的字符串以用于调试时,util.inspect 提供了比 console.log 默认行为更多的控制选项。它可以将嵌套深、包含循环引用的对象转化为结构清晰的字符串,而不会直接输出 [Object]

const util = require('util');

const obj = {
  name: 'server',
  options: {
    port: 3000,
    host: 'localhost'
  },
  status: 'running'
};

// 自定义显示深度和颜色
console.log(util.inspect(obj, { depth: 2, colors: true }));

输出结果类似:

{
  name: 'server',
  options: { port: 3000, host: 'localhost' },
  status: 'running'
}

util.inspect 常见的配置选项有:

  • depth:递归深度,设为 null 表示无限深度。
  • colors:是否输出 ANSI 颜色代码,方便终端阅读。
  • compact:是否紧凑显示,false 时每个属性会单独一行。
  • breakLength:每行最大长度,超过会换行。

在实际调试中,util.inspectJSON.stringify 更强大,因为它不会忽略不可枚举属性、Symbol 键以及循环引用。你也可以在自定义对象上添加 [util.inspect.custom] 方法来定义自己的查看输出。

8.4.6 util.deprecate —— 标注废弃 API

当开发一个库或框架时,可能会需要提醒用户某些 API 已经过时。util.deprecate 会包装原函数,在第一次被调用时向 stderr 输出一条废弃警告,同时仍然执行原函数逻辑。

const util = require('util');

function oldMethod() {
  // 原来的逻辑
}

const newMethod = util.deprecate(oldMethod, 'oldMethod is deprecated, use newMethod instead');

newMethod(); // 执行前会在 stderr 输出废弃提示

实际输出:(node:12345) DeprecationWarning: oldMethod is deprecated, use newMethod instead

这个方法在 Node.js 内部模块中大量使用,用于渐进淘汰旧 API。在生产环境中,可以通过 --no-deprecation--throw-deprecation 命令行标志来控制废弃警告的行为。

8.4.7 其他实用工具

除了上述高频方法,util 还提供了一些其他有用的辅助功能:

  • util.callbackify:与 promisify 相反,将返回 Promise 的异步函数转回错误优先的回调风格。
  • util.isDeepStrictEqual:深度比较两个值,是 assert.deepStrictEqual 的比较逻辑基础,比 === 更适合复杂对象结构。
  • util.types.isArrayBufferutil.types.isMaputil.types.isSet 等:提供对内置类型的精确判断。

例如,快速比较两个配置对象:

const util = require('util');

const config1 = { a: 1, b: { c: 2 } };
const config2 = { a: 1, b: { c: 2 } };
console.log(util.isDeepStrictEqual(config1, config2)); // true

8.4.8 使用建议与注意事项

  1. 优先采用现代替代方案:在支持 async/await 的新项目中,优先使用 fs.promises 或内建 Promise 版本 API,减少对 promisify 的依赖。继承优先使用 ES6 class extends 而不是 util.inherits
  2. 类型检查推荐用 util.types:不要重新发明轮子,用内建工具可以写出更自文档化的代码。
  3. 注意废弃 API 的使用:阅读 Node.js 文档时,留意标记为 Deprecated 的工具,避免将已废弃的 API 带入新项目。

util 模块虽然不如 fshttp 那样高频曝光,但它提供的是一套让代码更健壮、更干净的“幕后角色”。熟悉这些工具,能够让你在阅读 Node.js 源码、处理回调遗留代码、调试复杂对象时更加得心应手。