掘金 后端 ( ) • 2024-04-18 18:59

Nodejs中,有一个系统模块,名为Util。笔者理解,这就是一个工具类,有时也称为Utility或者Helper,是一些常用的,方便使用的,帮助开发的功能和方法集合。

由于它的分类,本来就比较杂,很多内容在普通的开发过程中可能用到的机会也不是特别大,这里笔者尝试对其进行分类,并列举几个比较常用或者特别的项目。

类和定义

util是一个nodejs的内置类,它的完整类定义如下:

[root@john-eosdev xdata3]# node
Welcome to Node.js v21.7.2.
Type ".help" for more information.
> util
{
  _errnoException: [Function: _errnoException],
  _exceptionWithHostPort: [Function: _exceptionWithHostPort],
  _extend: [Function: _extend],
  callbackify: [Function: callbackify],
  debug: [Function: debuglog],
  debuglog: [Function: debuglog],
  deprecate: [Function: deprecate],
  format: [Function: format],
  styleText: [Function: styleText],
  formatWithOptions: [Function: formatWithOptions],
  getSystemErrorMap: [Function: getSystemErrorMap],
  getSystemErrorName: [Function: getSystemErrorName],
  inherits: [Function: inherits],
  inspect: [Function: inspect] {
    custom: Symbol(nodejs.util.inspect.custom),
    colors: [Object: null prototype] {
      reset: [Array],
      bold: [Array],
      dim: [Array],
      italic: [Array],
      underline: [Array],
      blink: [Array],
      inverse: [Array],
      hidden: [Array],
      strikethrough: [Array],
      doubleunderline: [Array],
      black: [Array],
      red: [Array],
      green: [Array],
      yellow: [Array],
      blue: [Array],
      magenta: [Array],
      cyan: [Array],
      white: [Array],
      bgBlack: [Array],
      bgRed: [Array],
      bgGreen: [Array],
      bgYellow: [Array],
      bgBlue: [Array],
      bgMagenta: [Array],
      bgCyan: [Array],
      bgWhite: [Array],
      framed: [Array],
      overlined: [Array],
      gray: [Array],
      redBright: [Array],
      greenBright: [Array],
      yellowBright: [Array],
      blueBright: [Array],
      magentaBright: [Array],
      cyanBright: [Array],
      whiteBright: [Array],
      bgGray: [Array],
      bgRedBright: [Array],
      bgGreenBright: [Array],
      bgYellowBright: [Array],
      bgBlueBright: [Array],
      bgMagentaBright: [Array],
      bgCyanBright: [Array],
      bgWhiteBright: [Array]
    },
    styles: [Object: null prototype] {
      special: 'cyan',
      number: 'yellow',
      bigint: 'yellow',
      boolean: 'yellow',
      undefined: 'grey',
      null: 'bold',
      string: 'green',
      symbol: 'green',
      date: 'magenta',
      regexp: 'red',
      module: 'underline'
    },
    replDefaults: [Getter/Setter]
  },
  isArray: [Function: isArray],
  isBoolean: [Function: isBoolean],
  isBuffer: [Function: isBuffer],
  isDeepStrictEqual: [Function: isDeepStrictEqual],
  isNull: [Function: isNull],
  isNullOrUndefined: [Function: isNullOrUndefined],
  isNumber: [Function: isNumber],
  isString: [Function: isString],
  isSymbol: [Function: isSymbol],
  isUndefined: [Function: isUndefined],
  isRegExp: [Function: isRegExp],
  isObject: [Function: isObject],
  isDate: [Function: isDate],
  isError: [Function: isError],
  isFunction: [Function: isFunction],
  isPrimitive: [Function: isPrimitive],
  log: [Function: log],
  promisify: [Function: promisify] { custom: Symbol(nodejs.util.promisify.custom) },
  stripVTControlCharacters: [Function: stripVTControlCharacters],
  toUSVString: [Function: toUSVString],
  transferableAbortSignal: [Getter],
  transferableAbortController: [Getter],
  aborted: [Getter],
  types: {
    isExternal: [Function: isExternal],
    isDate: [Function: isDate],
    isArgumentsObject: [Function: isArgumentsObject],
    isBigIntObject: [Function: isBigIntObject],
    isBooleanObject: [Function: isBooleanObject],
    isNumberObject: [Function: isNumberObject],
    isStringObject: [Function: isStringObject],
    isSymbolObject: [Function: isSymbolObject],
    isNativeError: [Function: isNativeError],
    isRegExp: [Function: isRegExp],
    isAsyncFunction: [Function: isAsyncFunction],
    isGeneratorFunction: [Function: isGeneratorFunction],
    isGeneratorObject: [Function: isGeneratorObject],
    isPromise: [Function: isPromise],
    isMap: [Function: isMap],
    isSet: [Function: isSet],
    isMapIterator: [Function: isMapIterator],
    isSetIterator: [Function: isSetIterator],
    isWeakMap: [Function: isWeakMap],
    isWeakSet: [Function: isWeakSet],
    isArrayBuffer: [Function: isArrayBuffer],
    isDataView: [Function: isDataView],
    isSharedArrayBuffer: [Function: isSharedArrayBuffer],
    isProxy: [Function: isProxy],
    isModuleNamespaceObject: [Function: isModuleNamespaceObject],
    isAnyArrayBuffer: [Function: isAnyArrayBuffer],
    isBoxedPrimitive: [Function: isBoxedPrimitive],
    isArrayBufferView: [Function: isView],
    isTypedArray: [Function: isTypedArray],
    isUint8Array: [Function: isUint8Array],
    isUint8ClampedArray: [Function: isUint8ClampedArray],
    isUint16Array: [Function: isUint16Array],
    isUint32Array: [Function: isUint32Array],
    isInt8Array: [Function: isInt8Array],
    isInt16Array: [Function: isInt16Array],
    isInt32Array: [Function: isInt32Array],
    isFloat32Array: [Function: isFloat32Array],
    isFloat64Array: [Function: isFloat64Array],
    isBigInt64Array: [Function: isBigInt64Array],
    isBigUint64Array: [Function: isBigUint64Array],
    isKeyObject: [Function: value],
    isCryptoKey: [Function: value]
  },
  parseEnv: [Function: parseEnv],
  parseArgs: [Getter/Setter],
  TextDecoder: [Getter/Setter],
  TextEncoder: [Getter/Setter],
  MIMEType: [Getter/Setter],
  MIMEParams: [Getter/Setter]
}
>


虽然,util是一个nodejs的内置模块,但不是全局变量,需要直接引用,然后就可以使用了。

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

isDeepStrictEqual

这是一个比较有用的功能,用于判断两个对象的内容和值是完全相等的。如果没有这个方法,自己来实现,需要考虑到很多的因素,也不一定能够做得很好。

类型判断

使用util进行类型判断,是一个比较常见的需求和场景。从其类定义来看,可以直接进行的类型判断包括:

Array数组、Boolean布尔值、Buffer缓冲区、Null空、Undefined未定义、NullOrUndefined空或未定义、Number数字、String字符串、Symbol符号、RegExp正则、Object对象、Date日期、Error错误、Function方法、Primitive基础类型等等。

虽然用的比较少,但笔者看到,这些方法似乎比JS的typeof或者instanceof好像要细致和优雅,但它们并不是标准的JS方法,这样就在前端受到了一些限制,有机会希望可以尝试使用。

在更新版本的nodejs中,这一系列的类型判断函数已经被标记为“过期”。推荐的处理方式是使用util.types类的对应的判断方法,或者类型本身的判断方式,如Array.isArray,Buffer.isBuffer等等。除了上述的类型判断之外,它还提供了更细粒度的类型检查和判断,我们在其类定义里面也可以看到。读者有兴趣可以研读相关的技术文档。

Debug 调试

util.debuglog方法,用于创建一个调试方法,它可以基于环境变量的设置,条件化的输出调试内容到stderr接口。下面的示例代码方便我们进行理解:

//  NODE_DEBUG = foo

const util = require('node:util');
const debuglog = util.debuglog('foo');

debuglog('hello from foo [%d]', 123); 

// 输出 
FOO 3245: hello from foo [123] 

这段代码说明:

  • 可以用debuglog方法,来创建一个日志方法
  • 参数是一个环境变量值
  • 当NODE_DEBUG环境变量设置为该值是,debuglog方法就可以执行了
  • 如果没有这个环境变量,则定义的debuglog不会实际执行,也不会输出内容
  • 如果有输出信息,则会包括环境变量和当前进程编号

还有一个util.debug方法,其实就是debuglog方法的别名。使用util.debug,开发者可以有条件的控制调试信息的输出。

Format 格式化

这个方法类似于C语言中的printf函数。它的输入包括一个模板字符串和一系列数值,输出是格式化后的字符串。format是同步方法,通常用作调试信息的显示和输出。

下面是其用法的简单的参考代码:


util.format('%s:%s', 'foo');
// Returns: 'foo:%s' 

util.format('%s:%s', 'foo', 'bar', 'baz');
// Returns: 'foo:bar baz'

这里我们可以看到一些要点:

  • format方法的第一个参数,通常是带有类型变量占位符的字符串
  • format后续参数,将会按照类型定义,替换格式字符串中的占位符

format格式模板可选项目包括:

  • %s 字符串
  • %d 数字
  • %i 整数
  • %f 浮点数
  • %j JSON
  • %o 对象
  • %c CSS
  • %o 对象
  • %% 百分号转义

util还有一个相关的formatWithOptions方法,可以通过提供更多的选项,来丰富模板字符串的操作。

Inspect 检查器

我们在开发和调试的过程中,经常遇到需要打印对象内容的场景。默认的console.log方法可能不能够满足我们的需求,因为它可能会将内部的对象打印称为"[object]"这种方式,或者,它会忽略输出一些内容,这显然不是我们需要的。

这时,我们可以将console.log和util.inspect方法结合起来使用,它可以用于输出完整的JS对象的内容。下面是一个简单的示例:


const { inspect } = require('node:util');

const obj = {};
obj.a = [obj];
obj.b = {};
obj.b.inner = obj.b;
obj.b.obj = obj;

console.log(inspect(obj));
// <ref *1> {
//   a: [ [Circular *1] ],
//   b: <ref *2> { inner: [Circular *2], obj: [Circular *1] }
// } 

这只是一个非常简单的示例和场景。实际上util的inspect方法功能非常强大,有很多额外的选项,比如可以自定义输出(自定义inspect方法),配置输出内容的格式包括颜色,选择是否显示非可枚举对象,显示深度,控制显示内容的大型长度,排序等等,有兴趣的读者可以自行查阅相关技术文档,获取更详细的信息。

MIMEType

在当前的版本中,这是一个实验性的特性。MIMEType就是对mime类型的支持。下面的例子让我们能够很容易的理解这一点:


const { MIMEType } = require('node:util');

const myMIME = new MIMEType('text/javascript');
console.log(myMIME.type);
// Prints: text
myMIME.type = 'application';
console.log(myMIME.type);
// Prints: application
console.log(String(myMIME));
// Prints: application/javascript

有趣的是,不知道为什么nodejs社区会将这个功能设计在util模块中,笔者个人觉得,将mimetype设计到http模块中,或者作为一个独立的模块,可能更会更合适一点吧。

parseArgs/parseEnv 解析参数和环境变量

parseArgs可以用于解析process.argv中的内容,可以通过选项参数来控制内容和转换的规则,最后输出一个参数对象。如下面的示例:

const { parseArgs } = require('node:util');
const args = ['-f', '--bar', 'b'];
const options = {
  foo: {
    type: 'boolean',
    short: 'f',
  },
  bar: {
    type: 'string',
  },
};
const {
  values,
  positionals,
} = parseArgs({ args, options });
console.log(values, positionals);
// Prints: [Object: null prototype] { foo: true, bar: 'b' } []

和parseArg的诉求和原理类似,parseEnv可以将.env文件中的内容,转换为一个对象。

Promisify Promise化

此方法可以将一个异步回调函数,转换称为一个Promise对象,然后使用Promise的方法来执行。比如下面这个简单的示例:

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

const stat = util.promisify(fs.stat);

stat('.').then((stats) => {
  // Do something with `stats`
}).catch((error) => {
  // Handle the error.
}); 

// async/await 调用方式
async function callStat() {
  const stats = await stat('.');
  console.log(`This directory is owned by ${stats.uid}`);
}

callStat(); 

直接使用util.promisify,来对某个异步回调函数进行promise化,其实有一个条件,就是它的callback方法的参数,必须是(error,data)这种形式的,如果不是这种类型,可能需要使用util.promisify.custom来完全自定义方法的promise化,这种情况称为Custom Promisified Functions,自定义Promise函数。

Encoder/Decoder 文本编解码器

这个没有什么特别的,应该就是WHATWH Encoding标准的 text Encoder/Decoder API的实现。和全局变量TexeEncoder/Decoder好像没什么差异。

Log 日志

可以向标准stdout输出带有时间戳的调试信息。


> util.log('Timestamped message.');
18 Apr 11:32:29 - Timestamped message.

deprecate 弃用

我们在开发工作中,经常会遇到某个功能版本被标记成为“弃用”的情况。这是一种在软件开发行业在系统演进过程中,尽量保证这个过程平稳过渡的管理和控制机制。它的基础原理是,通过一种一致的约定和规范,可以选择将一个功能、API或者函数标记成为“弃用”,这时并不意味着它会被立刻删除或者停用,但它会在被使用时开发者和用户都会收到一些提示信息;这样在一个过渡周期当中,开发者就可以优先选择使用新的特性或者替代技术方案;最终经过一段时间的发展,当源头开发者看到老版本的使用降低到某个程度之后,就可以真正考虑将老特性从系统中移除了。

关于弃用机制,开发者应该理解以下要点:

  • 弃用(deprecated)不等于立即删除

不用过于惊慌,它依然可以在当前甚至相当长的一段时间内可用,但应当在新系统或者更新时考虑改进和替代的技术方案。

  • 向后兼容性

弃用通常是为了保持向后兼容性,给开发人员一个过渡期去适应变更,并修改代码来适应这个弃用,这样可以防止意外破坏现有系统。

  • 删除计划和版本

弃用是对未来移除该功能特性的一个预告和警告。它向开发人员发出信号,某些遗留功能在未来版本中计划被删除。一个良好的弃用方案还可能包括弃用的版本规划,即表明在那些版本可能实施真正的移除操作。

  • 替代方案

作为原始代码的开发者,如果考虑将特性标记成为“弃用”时,应当提供相关的升级或替代方案。

  • 编译器/IDE警告

作为一致的约定,大多数编译器和IDE会显示警告信息提醒开发者,正在使用的代码元素已被标记为deprecated,需要注意更新。此外一些弃用信息也会出现在程序启动、调试或者运行过程中,对开发者和用户发出提示。

nodejs的util模块,就提供了弃用信息注入的方法。我们可以通过示例代码来简单了解一下:

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

const fn1 = util.deprecate(someFunction, someMessage, 'DEP0001');
const fn2 = util.deprecate(someOtherFunction, someOtherMessage, 'DEP0001');
fn1(); // Emits a deprecation warning with code DEP0001
fn2(); // Does not emit a deprecation warning because it has the same code 

这些弃用信息在开发的过程中,可能确实对于开发者是有用的,但在生产环境中,可能会带来一些困扰。这时可以通过一些启动选择,可选不显示这些信息。如 --no-deprecation 或者 --no-warnings等。

其他杂项

util的其他杂项包括:

  • getSystemErrorName(err) 获取错误的系统名称
  • getSystemErrorMap() 获取相关所有系统错误列表
  • callBackify(original) 将一个同步调用方法,转换称为异步回调的模式
  • styleText() 将文本风格化
  • stripVTControlCharacters(str) 剥离逃逸码
  • transferableAbortController/transferableAbortSignal 撤销控制相关