Node 模块解读第四篇 util 模块, 之前已经介绍过 vm 模块, Buffer 模块, Event 模块 .
util 模块最初的目的是为内部 API 提供一些工具支持, 然而很多工具函数对于普通的开发者来说也十分有用, 因此 util 模块将一些方法实现了对外暴露. 本文主要探讨以下三方面的工具函数:
风格转换
调试输出
废弃 API
风格转换
callback 转换 promise
针对传入 error-first 回调作为函数的最后一个参数的函数 (比如 fs.readFile('./filename', (err, data) => {})), util 提供了 promisify(original) 方法用来将这种类型的函数转换成返回 promise 的形式.
以 fs.readFile 为例
- const fs = require('fs');
- fs.readFile('./h.js', (err, data) => {
- if (err) {
- console.error(err);
- return;
- }
- console.log(data.toString());
- })
- // 使用 util.promisify 转换后
- const fs = require('fs');
- const util = require('util');
- const readFilePromise = util.promisify(fs.readFile);
- readFilePromise('./h.js')
- .then((data) => {
- console.log(data.toString());
- }, (err) => {
- console.error(err);
- });
具体实现
promisify 执行完后返回的是一个新的函数, 新的函数的执行结果是一个 promise, 新函数内部会调用 original 原有的方法并且会自动追加 error-first 类型的 callback, 根据 original 的执行结果判断是 resolve 还是 reject, 简易版本的代码如下:
- function promisify(original) {
- function fn(...args) {
- const promise = createPromise();
- try {
- original.call(this, ...args, (err, ...values) => {
- if (err) {
- promiseReject(promise, err);
- } else {
- promiseResolve(promise, values[0]);
- }
- });
- } catch (err) {
- promiseReject(promise, err);
- }
- return promise;
- }
- return fn
- }
util 模块还提供了 promisify 的自定义转换方式(original 函数上定义 util.promisify.custom 属性), 比如通过下面的方式可以实现禁用文件读取, util.promisify.custom 必须定义在 util.promisify 调用之前
- const fs = require('fs');
- const util = require('util');
- fs.readFile[util.promisify.custom] = (fileName) => {
- return Promise.reject('not allowed');
- }
- const readFilePromise = util.promisify(fs.readFile);
- readFilePromise('./h.js')
- .then((data) => {
- console.log(data.toString());
- }, (err) => {
- console.error(err); // not allowed
- });
promise 转 callback
util 的 callbackify 方法与 promisify 刚好相反, callbackify 用于把 async(或者返回 promise)的函数转换成遵从 error-first 回调风格的类型
- const util = require('util');
- const fn = () => {
- return 'fn executed'
- };
- function delay(second, fn) {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve(fn());
- }, second);
- });
- }
- delay(1000, fn)
- .then((data) => {
- console.log(data); // fn executed
- });
- // 使用 util.callbackify 转换后
- const delayFn = util.callbackify(delay);
- delayFn(1000, fn, (err, data) => {
- if (err) {
- console.error(err);
- return;
- }
- console.log(data); // fn executed
- });
有一种情况需要关注, 假如 promise 是 reject(null || 0 || false)的话, 那么 callback 的 err 判断为非, 程序会继续执行 console.log(data), 这其实是不正确的. 因此 callbackify 对于这种情况做了特殊的处理(创建一个 error, 将原始的信息放在 error 的 reason 属性上, 把 error 传递给最终的回调函数)
- function callbackifyOnRejected(reason, cb) {
- if (!reason) {
- const newReason = new ERR_FALSY_VALUE_REJECTION();
- newReason.reason = reason;
- reason = newReason;
- Error.captureStackTrace(reason, callbackifyOnRejected);
- }
- return cb(reason);
- }
具体实现
实现的逻辑是调用原始函数 original 通过 then 来调用 callback 方法
- function callbackify(original) {
- function callbackified(...args) {
- const maybeCb = args.pop();
- const cb = (...args) => { Reflect.apply(maybeCb, this, args); };
- Reflect.apply(original, this, args)
- .then((ret) => process.nextTick(cb, null, ret),
- (rej) => process.nextTick(callbackifyOnRejected, rej, cb));
- }
- return callbackified;
- }
调试输出
util.debuglog(section)
debuglog 方法用于根据 NODE_DEBUG 环境变量来选择性的输出 debug 信息, 例如下面的例子
- //index.js
- const util = require('util');
- const debuglog = util.debuglog('foo-bar');
- debuglog('hello from foo [%d]', 123);
- // 执行 index 文件
- node index.js // 没有输出
- NODE_DEBUG=foo-bar node index.js //FOO-BAR 18470: hi there, it's foo-bar [2333]
NODE_DEBUG 如果希望输出多个 section 可以用逗号做分隔, 同时 NODE_DEBUG 也支持通配符形式(node 版本需要 10)
- NODE_DEBUG=fs,net,tls // 多个 section
- NODE_DEBUG=foo*// 通配符
上面的 debuglog 函数执行的时候支持占位符, 其实底层使用的是 util.format 方法.
util.format
util.format 用于占位符替换, 不同类型的数据采用不同的占位符表示
%s | 字符串 |
---|---|
%d | 整数或浮点数 |
%i | 整数 |
%f | 浮点数 |
%j | JSON |
%o | Object(包括不可枚举的属性) |
%O | Object(不包括不可枚举的属性) |
%% | 输出 % |
对 Object 格式化输出字符串的时候用到的其实是 util.inspect 方法,%o 与 %O 的区别仅在于调用 inspect 方法时传入配置项有别
%o 传入 util.inspect 的配置项是 { showHidden: true, showProxy: true }
util.inspect
util.inspect 用于对 object 做格式化字符串操作, 并提供个性化配置项
- const util = require('util');
- var child = {
- "name": "child",
- "age": 18,
- "friends": [
- {
- "name": "randal",
- "age" : 19,
- "friends": [
- {
- "name": "Kate",
- "age": 18
- }
- ]
- }
- ],
- "motto": "Now this is not the end. It is not even the beginning of the end. But it is, perhaps, the end of the beginning."
- }
- console.log(util.inspect(child, { compact: false, depth: null, breakLength: 80}));
compact 用于各属性独占一行显示
depth 用于控制显示层级, 默认是 2
breakLength 用于一行文字的最大个数, 超出换行
更详细的参数及释义参见官网 https://nodejs.org/dist/latest-v10.x/docs/api/util.html#util_util_inspect_object_options
废弃 API
使用 util.deprecate 方法可以针对废弃的 api 在终端输出废弃的提示信息
- const util = require('util');
- const oldFn = () => {
- console.log('old fn');
- };
- class oldClass {
- constructor() {
- console.log('old class');
- }
- }
- const fn1 = util.deprecate(oldFn, 'deprecated fn');
- const fn2 = util.deprecate(oldClass, 'deprecated class');
- fn1();
- new fn2();
- // 输出
- old fn
- old class
- (node:18001) DeprecationWarning: deprecated fn
- (node:18001) DeprecationWarning: deprecated class
具体实现
- function deprecate(fn, msg, code) {
- let warned = false;
- function deprecated(...args) {
- if (!warned) {
- warned = true;
- if (code !== undefined) {
- if (!codesWarned[code]) {
- process.emitWarning(msg, 'DeprecationWarning', code, deprecated); // emit 警告信息
- codesWarned
- = true; // 避免同一 errorCode 重复提示
- }
- } else {
- process.emitWarning(msg, 'DeprecationWarning', deprecated);
- }
- }
- if (new.target) { // class 类型
- return Reflect.construct(fn, args, new.target);
- }
- return fn.apply(this, args); // 函数类型
- }
- return deprecated;
- }
与此相关的命令行配置项
命令行选项 | process 属性 | |
---|---|---|
不输出警告信息 | --no-deprecation --no-warnings | noDeprecation |
输出详细的堆栈信息 | --trace-deprecation --trace-warnings | traceDeprecation |
抛出错误异常 | --throw-deperecation | throwDeprecation |
其他
除了前面提到的三方面的工具外, util.types 下还提供了非常丰富的类型识别的方法 (isGeneratorFunction,isWeakSet,isPromise,isArrayBuffer) 等, 以后在需要类型判断的时候可以考虑使用 util 模板提供的这些方法.
参考资料
https://nodejs.org/dist/latest-v10.x/docs/api/util.html
来源: https://juejin.im/post/5b029603f265da0b722b6df5